Files
archived-vdo.ninja/multi.html
steveseguin a631bc074c v29.0
2026-01-18 03:27:00 -05:00

619 lines
20 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>iFrame Mixer</title>
<style>
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--bg-tertiary: #e0e0e0;
--text-primary: #000000;
--text-secondary: #666666;
--border-color: #cccccc;
--button-primary: #4CAF50;
--button-danger: #f44336;
--shadow: rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2a2a2a;
--bg-tertiary: #3a3a3a;
--text-primary: #ffffff;
--text-secondary: #cccccc;
--border-color: #444444;
--button-primary: #45a049;
--button-danger: #d32f2f;
--shadow: rgba(0, 0, 0, 0.3);
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
overflow: hidden;
height: 100vh;
width: 100vw;
}
#settings-page {
padding: 20px;
max-width: 600px;
margin: 0 auto;
height: 100vh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
text-align: center;
}
.url-input-container {
margin-bottom: 15px;
position: relative;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.url-input-wrapper {
display: flex;
gap: 10px;
align-items: center;
}
input[type="text"], input[type="url"] {
flex: 1;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
background-color: var(--bg-secondary);
color: var(--text-primary);
font-size: 16px;
transition: border-color 0.3s;
}
input[type="text"]:focus, input[type="url"]:focus {
outline: none;
border-color: var(--button-primary);
}
select {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
background-color: var(--bg-secondary);
color: var(--text-primary);
font-size: 16px;
margin-bottom: 15px;
cursor: pointer;
}
button {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
}
.btn-primary {
background-color: var(--button-primary);
color: white;
width: 100%;
margin-bottom: 10px;
}
.btn-primary:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.btn-secondary {
background-color: var(--bg-tertiary);
color: var(--text-primary);
}
.btn-danger {
background-color: var(--button-danger);
color: white;
padding: 8px 16px;
font-size: 14px;
}
.btn-add {
background-color: var(--bg-tertiary);
color: var(--text-primary);
width: 100%;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-add:hover {
background-color: var(--border-color);
}
#iframe-container {
display: none;
width: 100vw;
height: 100vh;
position: relative;
background-color: #000;
}
iframe {
border: none;
position: absolute;
background-color: #000;
}
.layout-over-under iframe {
width: 100%;
height: 50%;
}
.layout-over-under iframe:nth-child(1) {
top: 0;
left: 0;
}
.layout-over-under iframe:nth-child(2) {
top: 50%;
left: 0;
}
.layout-side-by-side iframe {
width: 50%;
height: 100%;
top: 0;
}
.layout-side-by-side iframe:nth-child(1) {
left: 0;
}
.layout-side-by-side iframe:nth-child(2) {
left: 50%;
}
.layout-pip iframe:nth-child(1) {
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.layout-pip iframe:nth-child(2) {
width: 30%;
height: 20%;
bottom: 20px;
right: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
border-radius: 8px;
z-index: 10;
}
.layout-overlay iframe {
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.layout-overlay iframe:nth-child(n+2) {
opacity: 0.8;
}
.layout-tabs {
position: relative;
}
.layout-tabs iframe {
width: 100%;
height: calc(100% - 50px);
top: 50px;
left: 0;
display: none;
}
.layout-tabs iframe.active {
display: block;
}
.tab-bar {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 50px;
background-color: var(--bg-secondary);
display: flex;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-bottom: 1px solid var(--border-color);
}
.tab {
padding: 10px 20px;
cursor: pointer;
white-space: nowrap;
border-right: 1px solid var(--border-color);
transition: background-color 0.3s;
display: flex;
align-items: center;
font-size: 14px;
}
.tab:hover {
background-color: var(--bg-tertiary);
}
.tab.active {
background-color: var(--button-primary);
color: white;
}
.floating-controls {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
display: none;
}
.floating-btn {
width: 56px;
height: 56px;
border-radius: 50%;
background-color: var(--button-primary);
color: white;
border: none;
box-shadow: 0 4px 8px var(--shadow);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.floating-btn:hover {
transform: scale(1.1);
}
.preview-container {
background-color: var(--bg-secondary);
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.preview-title {
font-weight: 600;
margin-bottom: 10px;
color: var(--text-secondary);
}
.preview-item {
padding: 8px;
margin-bottom: 5px;
background-color: var(--bg-tertiary);
border-radius: 4px;
font-size: 14px;
word-break: break-all;
}
@media (max-width: 600px) {
.layout-side-by-side iframe {
width: 100%;
height: 50%;
}
.layout-side-by-side iframe:nth-child(1) {
top: 0;
left: 0;
}
.layout-side-by-side iframe:nth-child(2) {
top: 50%;
left: 0;
}
.layout-pip iframe:nth-child(2) {
width: 40%;
height: 25%;
bottom: 10px;
right: 10px;
}
}
@media (orientation: landscape) and (max-width: 800px) {
.layout-over-under iframe {
width: 50%;
height: 100%;
}
.layout-over-under iframe:nth-child(1) {
top: 0;
left: 0;
}
.layout-over-under iframe:nth-child(2) {
top: 0;
left: 50%;
}
}
</style>
</head>
<body>
<div id="settings-page">
<h1>iFrame Mixer</h1>
<div id="url-inputs">
<div class="url-input-container">
<div class="url-input-wrapper">
<input type="url" placeholder="Enter URL 1" class="url-input" />
<button class="btn-danger" onclick="removeUrlInput(this)">×</button>
</div>
</div>
<div class="url-input-container">
<div class="url-input-wrapper">
<input type="url" placeholder="Enter URL 2" class="url-input" />
<button class="btn-danger" onclick="removeUrlInput(this)">×</button>
</div>
</div>
</div>
<button class="btn-add" onclick="addUrlInput()">
<span>+</span> Add Another URL
</button>
<label for="layout-select" style="display: block; margin-bottom: 8px; font-weight: 500;">Layout:</label>
<select id="layout-select">
<option value="over-under">Over/Under</option>
<option value="side-by-side">Side by Side</option>
<option value="pip">Picture in Picture</option>
<option value="overlay">Transparent Overlay</option>
<option value="tabs">Tabs</option>
</select>
<div id="preview-container" class="preview-container" style="display: none;">
<div class="preview-title">Previous Settings:</div>
<div id="preview-content"></div>
</div>
<button class="btn-primary" onclick="startMixer()">Start Mixer</button>
<button class="btn-secondary" onclick="clearSettings()" style="width: 100%;">Clear Saved Settings</button>
</div>
<div id="iframe-container">
<div id="tab-bar" class="tab-bar" style="display: none;"></div>
<div id="frames-container"></div>
</div>
<div class="floating-controls" id="floating-controls">
<button class="floating-btn" onclick="location.reload()"></button>
</div>
<script>
let urlCount = 2;
let currentTab = 0;
function loadSavedSettings() {
const saved = localStorage.getItem('iframeMixerSettings');
if (saved) {
const settings = JSON.parse(saved);
const preview = document.getElementById('preview-container');
const previewContent = document.getElementById('preview-content');
preview.style.display = 'block';
previewContent.innerHTML = '';
settings.urls.forEach((url, index) => {
const item = document.createElement('div');
item.className = 'preview-item';
item.textContent = `URL ${index + 1}: ${url}`;
previewContent.appendChild(item);
});
const layoutItem = document.createElement('div');
layoutItem.className = 'preview-item';
layoutItem.textContent = `Layout: ${settings.layout.replace('-', ' ')}`;
previewContent.appendChild(layoutItem);
const urlInputs = document.querySelectorAll('.url-input');
settings.urls.forEach((url, index) => {
if (index < urlInputs.length) {
urlInputs[index].value = url;
} else {
addUrlInput();
const newInputs = document.querySelectorAll('.url-input');
newInputs[newInputs.length - 1].value = url;
}
});
document.getElementById('layout-select').value = settings.layout;
}
}
function saveSettings() {
const urls = Array.from(document.querySelectorAll('.url-input'))
.map(input => input.value)
.filter(url => url.trim() !== '');
const layout = document.getElementById('layout-select').value;
const settings = { urls, layout };
localStorage.setItem('iframeMixerSettings', JSON.stringify(settings));
}
function clearSettings() {
if (confirm('Clear all saved settings?')) {
localStorage.removeItem('iframeMixerSettings');
location.reload();
}
}
function addUrlInput() {
urlCount++;
const container = document.createElement('div');
container.className = 'url-input-container';
container.innerHTML = `
<div class="url-input-wrapper">
<input type="url" placeholder="Enter URL ${urlCount}" class="url-input" />
<button class="btn-danger" onclick="removeUrlInput(this)">×</button>
</div>
`;
document.getElementById('url-inputs').appendChild(container);
}
function removeUrlInput(button) {
const containers = document.querySelectorAll('.url-input-container');
if (containers.length > 2) {
button.closest('.url-input-container').remove();
updatePlaceholders();
} else {
alert('You need at least 2 URLs');
}
}
function updatePlaceholders() {
const inputs = document.querySelectorAll('.url-input');
inputs.forEach((input, index) => {
input.placeholder = `Enter URL ${index + 1}`;
});
}
function startMixer() {
const urls = Array.from(document.querySelectorAll('.url-input'))
.map(input => input.value.trim())
.filter(url => url !== '');
if (urls.length < 2) {
alert('Please enter at least 2 URLs');
return;
}
saveSettings();
document.getElementById('settings-page').style.display = 'none';
const container = document.getElementById('iframe-container');
container.style.display = 'block';
const layout = document.getElementById('layout-select').value;
const framesContainer = document.getElementById('frames-container');
framesContainer.className = `layout-${layout}`;
if (layout === 'tabs') {
const tabBar = document.getElementById('tab-bar');
tabBar.style.display = 'flex';
urls.forEach((url, index) => {
const tab = document.createElement('div');
tab.className = 'tab' + (index === 0 ? ' active' : '');
tab.textContent = `Tab ${index + 1}`;
tab.onclick = () => switchTab(index);
tabBar.appendChild(tab);
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.className = index === 0 ? 'active' : '';
iframe.allow = 'accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi; clipboard-read; clipboard-write; payment; picture-in-picture; speaker-selection; usb; xr-spatial-tracking';
iframe.allowFullscreen = true;
iframe.allowTransparency = true;
framesContainer.appendChild(iframe);
});
} else {
urls.forEach((url, index) => {
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.allow = 'accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi; clipboard-read; clipboard-write; payment; picture-in-picture; speaker-selection; usb; xr-spatial-tracking';
iframe.allowFullscreen = true;
iframe.allowTransparency = true;
framesContainer.appendChild(iframe);
});
}
document.getElementById('floating-controls').style.display = 'block';
if ('pictureInPictureEnabled' in document) {
enablePictureInPicture();
}
}
function switchTab(index) {
const tabs = document.querySelectorAll('.tab');
const iframes = document.querySelectorAll('#frames-container iframe');
tabs.forEach((tab, i) => {
tab.classList.toggle('active', i === index);
});
iframes.forEach((iframe, i) => {
iframe.classList.toggle('active', i === index);
});
currentTab = index;
}
function enablePictureInPicture() {
document.addEventListener('visibilitychange', async () => {
if (document.hidden && document.pictureInPictureEnabled) {
try {
const videos = document.querySelectorAll('iframe');
for (const iframe of videos) {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const video = iframeDoc?.querySelector('video');
if (video && !video.disablePictureInPicture) {
await video.requestPictureInPicture();
break;
}
}
} catch (err) {
console.log('PiP not available:', err);
}
}
});
}
window.addEventListener('load', loadSavedSettings);
if ('serviceWorker' in navigator) {
window.addEventListener('beforeunload', (e) => {
const iframes = document.querySelectorAll('iframe');
if (iframes.length > 0 && document.getElementById('iframe-container').style.display !== 'none') {
e.preventDefault();
e.returnValue = '';
}
});
}
</script>
</body>
</html>