mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
619 lines
19 KiB
HTML
619 lines
19 KiB
HTML
<!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> |