Spaces:
Sleeping
Sleeping
| <html class="dark" lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta content="width=device-width, initial-scale=1.0" name="viewport" /> | |
| <title>Continuity</title> | |
| <link href="https://fonts.googleapis.com" rel="preconnect" /> | |
| <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect" /> | |
| <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" | |
| rel="stylesheet" /> | |
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@300;400;500;600;700&display=swap" | |
| rel="stylesheet" /> | |
| <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" | |
| rel="stylesheet" /> | |
| <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script> | |
| <script> | |
| tailwind.config = { | |
| darkMode: "class", | |
| theme: { | |
| extend: { | |
| colors: { "primary": "#7f0df2", "background-dark": "#0a060f", "surface-dark": "#1a1221", "border-dark": "#362445" }, | |
| fontFamily: { "display": ["Space Grotesk", "sans-serif"], "body": ["Noto Sans", "sans-serif"] }, | |
| boxShadow: { "neon": "0 0 20px rgba(127, 13, 242, 0.4)" } | |
| }, | |
| }, | |
| } | |
| </script> | |
| <style> | |
| body { | |
| background-color: #0a060f; | |
| } | |
| .glass-panel { | |
| background: rgba(26, 18, 33, 0.95); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| } | |
| .force-clip { | |
| -webkit-mask-image: -webkit-radial-gradient(white, black); | |
| mask-image: radial-gradient(white, black); | |
| transform: translateZ(0); | |
| border-radius: 1rem; | |
| overflow: hidden; | |
| } | |
| #gallery-drawer { | |
| transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .drawer-open { | |
| transform: translateX(0%); | |
| } | |
| .drawer-closed { | |
| transform: translateX(100%); | |
| } | |
| input[type=range] { | |
| -webkit-appearance: none; | |
| background: transparent; | |
| } | |
| input[type=range]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| height: 16px; | |
| width: 16px; | |
| border-radius: 50%; | |
| background: #7f0df2; | |
| margin-top: -6px; | |
| cursor: pointer; | |
| box-shadow: 0 0 10px rgba(127, 13, 242, 0.5); | |
| } | |
| input[type=range]::-webkit-slider-runnable-track { | |
| width: 100%; | |
| height: 4px; | |
| cursor: pointer; | |
| background: #362445; | |
| border-radius: 2px; | |
| } | |
| /* Custom Scrollbar */ | |
| .custom-scrollbar::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 4px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb { | |
| background: rgba(127, 13, 242, 0.5); | |
| border-radius: 4px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb:hover { | |
| background: rgba(127, 13, 242, 0.8); | |
| } | |
| </style> | |
| </head> | |
| <body | |
| class="bg-background-dark font-body text-white h-screen w-screen overflow-hidden flex flex-col selection:bg-primary selection:text-white relative"> | |
| <header | |
| class="flex items-center justify-between px-6 py-4 z-40 relative w-full border-b border-white/5 bg-background-dark/50 backdrop-blur-sm"> | |
| <div class="flex items-center gap-3"> | |
| <div | |
| class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary"> | |
| <span class="material-symbols-outlined">movie_filter</span></div> | |
| <h1 class="text-xl font-display font-bold tracking-tight">Continuity</h1> | |
| </div> | |
| <div class="flex items-center gap-3"> | |
| <button onclick="toggleDrawer(true)" | |
| class="flex items-center gap-2 px-4 py-2 bg-primary hover:bg-[#6b0bc9] text-white rounded-lg transition-colors text-xs font-bold uppercase tracking-wider shadow-neon"> | |
| <span class="material-symbols-outlined text-lg">history</span> Gallery | |
| </button> | |
| </div> | |
| </header> | |
| <main class="flex-1 w-full overflow-y-auto relative flex flex-col items-center pt-8 pb-[32rem]"> | |
| <div class="w-full max-w-6xl mx-auto flex items-center justify-center gap-4 md:gap-8 lg:gap-12 px-4"> | |
| <div class="flex flex-col gap-3 flex-1 max-w-[320px] group"> | |
| <div class="flex justify-between px-1"><span | |
| class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span> | |
| </div> | |
| <div class="relative aspect-[9/16] md:aspect-[3/4] bg-surface-dark border-2 border-dashed border-border-dark rounded-2xl flex flex-col items-center justify-center gap-4 hover:border-primary/50 hover:bg-surface-dark/80 transition-all cursor-pointer shadow-lg overflow-hidden" | |
| onclick="document.getElementById('video-upload-a').click()"> | |
| <div | |
| class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10"> | |
| <span | |
| class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span> | |
| </div> | |
| <p id="label-a" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload | |
| Start Clip</p> | |
| <input type="file" id="video-upload-a" accept="video/*" class="hidden" | |
| onchange="handleFileSelect(this, 'label-a')"> | |
| </div> | |
| </div> | |
| <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20"> | |
| <div class="flex justify-center px-1"><span | |
| class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated | |
| Bridge</span></div> | |
| <div id="bridge-card-outer" | |
| class="relative aspect-video rounded-2xl shadow-neon transition-all duration-500 border border-primary/20"> | |
| <div id="bridge-card-inner" class="force-clip w-full h-full bg-black relative"> | |
| <div id="bridge-content" class="w-full h-full"> | |
| <div | |
| class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1614850523060-8da1d56ae167?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-20 mix-blend-overlay"> | |
| </div> | |
| <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6"> | |
| <div | |
| class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3"> | |
| <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span> | |
| </div> | |
| <p class="text-sm text-gray-300">Ready to bridge the gap</p> | |
| </div> | |
| </div> | |
| <div id="bridge-border" | |
| class="absolute inset-0 rounded-2xl border-2 border-transparent pointer-events-none z-30"> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="merged-download-container" | |
| class="hidden flex justify-center mt-8 relative z-30 animate-in fade-in slide-in-from-top-2"> | |
| <a id="merged-download-btn" href="#" download | |
| class="flex items-center gap-2 px-6 py-3 bg-surface-dark hover:bg-white/5 border border-primary/30 hover:border-primary text-primary hover:text-white rounded-xl font-bold text-xs uppercase tracking-widest transition-all shadow-lg group"> | |
| <span class="material-symbols-outlined group-hover:animate-bounce">movie</span> Download Merged | |
| (A+B+C) | |
| </a> | |
| </div> | |
| </div> | |
| <div class="flex flex-col gap-3 flex-1 max-w-[320px] group"> | |
| <div class="flex justify-end px-1"><span | |
| class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span></div> | |
| <div class="relative aspect-[9/16] md:aspect-[3/4] bg-surface-dark border-2 border-dashed border-border-dark rounded-2xl flex flex-col items-center justify-center gap-4 hover:border-primary/50 hover:bg-surface-dark/80 transition-all cursor-pointer shadow-lg overflow-hidden" | |
| onclick="document.getElementById('video-upload-c').click()"> | |
| <div | |
| class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10"> | |
| <span | |
| class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span> | |
| </div> | |
| <p id="label-c" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload End | |
| Clip</p> | |
| <input type="file" id="video-upload-c" accept="video/*" class="hidden" | |
| onchange="handleFileSelect(this, 'label-c')"> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <div class="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 w-full max-w-2xl px-4 pointer-events-none"> | |
| <div id="analysis-panel" | |
| class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2 pointer-events-auto"> | |
| <div class="flex flex-col"><span class="text-sm font-bold text-white">Continuity Engine</span><span | |
| class="text-[10px] text-gray-400 uppercase tracking-wide">Ready for analysis</span></div> | |
| <div class="flex gap-2"> | |
| <button onclick="toggleDrawer(true)" | |
| class="bg-surface-dark hover:bg-white/10 text-white p-3 rounded-full transition-all flex items-center justify-center border border-white/10"><span | |
| class="material-symbols-outlined text-lg">history</span></button> | |
| <button id="analyze-btn" | |
| class="bg-primary hover:bg-[#6b0bc9] text-white px-6 py-3 rounded-full font-bold text-sm transition-all flex items-center gap-2 shadow-lg"><span | |
| class="material-symbols-outlined text-lg">analytics</span> Analyze Scenes</button> | |
| </div> | |
| </div> | |
| <div id="review-panel" | |
| class="hidden glass-panel rounded-2xl p-5 shadow-2xl flex flex-col gap-4 animate-in slide-in-from-bottom-4 duration-300 max-h-[80vh] overflow-y-auto custom-scrollbar pointer-events-auto"> | |
| <div class="flex items-center justify-between border-b border-white/10 pb-3"> | |
| <h3 class="text-sm font-bold text-white flex items-center gap-2"><span | |
| class="material-symbols-outlined text-primary">movie_edit</span> Director's Configuration</h3> | |
| <div class="flex gap-2"> | |
| <button onclick="toggleDrawer(true)" | |
| class="text-xs text-gray-400 hover:text-white uppercase tracking-wider flex items-center gap-1"><span | |
| class="material-symbols-outlined text-sm">history</span> History</button> | |
| <span class="text-white/10">|</span> | |
| <button onclick="resetUI()" | |
| class="text-xs text-gray-500 hover:text-white uppercase tracking-wider">Reset</button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4 mb-2"> | |
| <div class="bg-white/5 p-3 rounded-lg border border-white/10"> | |
| <span class="text-[10px] font-bold text-primary uppercase flex items-center gap-1"><span | |
| class="material-symbols-outlined text-xs">videocam</span> Scene A Analysis</span> | |
| <p id="analysis-a-text" | |
| class="text-[11px] text-gray-300 h-24 overflow-y-auto custom-scrollbar mt-2 leading-relaxed"> | |
| Waiting for analysis...</p> | |
| </div> | |
| <div class="bg-white/5 p-3 rounded-lg border border-white/10"> | |
| <span class="text-[10px] font-bold text-primary uppercase flex items-center gap-1"><span | |
| class="material-symbols-outlined text-xs">videocam</span> Scene C Analysis</span> | |
| <p id="analysis-c-text" | |
| class="text-[11px] text-gray-300 h-24 overflow-y-auto custom-scrollbar mt-2 leading-relaxed"> | |
| Waiting for analysis...</p> | |
| </div> | |
| </div> | |
| <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual | |
| Direction (Bridge B)</label> | |
| <textarea id="prompt-box" rows="4" | |
| class="w-full bg-black/20 border border-white/10 rounded-lg p-3 text-sm text-white focus:border-primary focus:ring-1 focus:ring-primary outline-none resize-none custom-scrollbar"></textarea> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual | |
| Style</label> | |
| <select id="style-select" onchange="savePreference('style', this.value)" | |
| class="w-full bg-black/20 border border-white/10 rounded-lg p-2.5 text-sm text-white focus:border-primary outline-none cursor-pointer"> | |
| <option value="Cinematic">Cinematic</option> | |
| <option value="Anime">Anime</option> | |
| <option value="Cyberpunk">Cyberpunk</option> | |
| <option value="VHS">VHS Glitch</option> | |
| <option value="Noir">Noir</option> | |
| </select> | |
| </div> | |
| <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio | |
| Mood</label> | |
| <select id="audio-input" onchange="savePreference('audio', this.value)" | |
| class="w-full bg-black/20 border border-white/10 rounded-lg p-2.5 text-sm text-white focus:border-primary outline-none cursor-pointer"> | |
| <option value="Cinematic orchestral score">Cinematic</option> | |
| <option value="Industrial synthwave">Cyberpunk</option> | |
| <option value="Nature sounds">Nature</option> | |
| <option value="Tense atmosphere">Horror</option> | |
| <option value="High energy rock">Action</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="border-t border-white/10 pt-3"> | |
| <button onclick="document.getElementById('advanced-settings').classList.toggle('hidden')" | |
| class="flex items-center gap-2 text-xs font-bold text-gray-400 uppercase tracking-widest hover:text-white transition-colors w-full"> | |
| <span class="material-symbols-outlined text-sm">tune</span> Advanced Physics & Controls <span | |
| class="material-symbols-outlined text-sm ml-auto">expand_more</span> | |
| </button> | |
| <div id="advanced-settings" | |
| class="hidden pt-3 grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in slide-in-from-top-2"> | |
| <div class="col-span-1 md:col-span-2"><label | |
| class="text-[10px] text-gray-500 uppercase font-bold">Negative Prompt</label> | |
| <input id="negative-prompt" type="text" placeholder="text, blurry, watermark" | |
| class="w-full bg-black/20 border border-white/10 rounded-lg p-2 text-xs text-white focus:border-red-500/50 outline-none mt-1"> | |
| </div> | |
| <div> | |
| <div class="flex justify-between"><label | |
| class="text-[10px] text-gray-500 uppercase font-bold">Guidance Scale</label><span | |
| id="guidance-val" class="text-[10px] text-primary">5.0</span></div> | |
| <input id="guidance-scale" type="range" min="1" max="20" value="5" step="0.5" | |
| class="w-full mt-2" | |
| oninput="document.getElementById('guidance-val').innerText = this.value"> | |
| </div> | |
| <div> | |
| <div class="flex justify-between"><label | |
| class="text-[10px] text-gray-500 uppercase font-bold">Motion Strength</label><span | |
| id="motion-val" class="text-[10px] text-primary">5</span></div> | |
| <input id="motion-strength" type="range" min="1" max="10" value="5" class="w-full mt-2" | |
| oninput="document.getElementById('motion-val').innerText = this.value"> | |
| </div> | |
| </div> | |
| </div> | |
| <a id="panel-download-btn" href="#" download | |
| class="hidden w-full bg-surface-dark border border-green-500/50 text-green-400 hover:bg-green-500/10 py-3.5 rounded-xl font-bold text-sm tracking-wide shadow-lg flex items-center justify-center gap-2 mb-3 transition-all"> | |
| <span class="material-symbols-outlined">download</span> Download Final Video | |
| </a> | |
| <button id="generate-btn" | |
| class="w-full bg-gradient-to-r from-primary to-[#9d4edd] hover:brightness-110 text-white py-3.5 rounded-xl font-bold text-sm tracking-wide shadow-lg flex items-center justify-center gap-2 mt-1"> | |
| <span class="material-symbols-outlined">auto_fix_high</span> Generate Video | |
| </button> | |
| </div> | |
| </div> | |
| <div id="drawer-overlay" onclick="toggleDrawer(false)" | |
| class="fixed inset-0 bg-black/80 backdrop-blur-sm z-[90] hidden transition-opacity"></div> | |
| <aside id="gallery-drawer" | |
| class="fixed top-0 right-0 h-full w-[400px] bg-background-dark border-l border-white/10 z-[100] drawer-closed flex flex-col shadow-2xl"> | |
| <div class="p-6 border-b border-white/5 flex items-center justify-between bg-surface-dark"> | |
| <h2 class="text-lg font-bold text-white flex items-center gap-2"><span | |
| class="material-symbols-outlined text-primary">history</span> History</h2> | |
| <button onclick="toggleDrawer(false)" | |
| class="text-gray-400 hover:text-white transition-colors cursor-pointer"><span | |
| class="material-symbols-outlined">close</span></button> | |
| </div> | |
| <div id="gallery-content" class="flex-1 overflow-y-auto p-4 space-y-4 custom-scrollbar"> | |
| <div class="text-center text-gray-500 mt-10">Loading history...</div> | |
| </div> | |
| </aside> | |
| <script> | |
| function toggleDrawer(show) { | |
| const drawer = document.getElementById('gallery-drawer'); | |
| const overlay = document.getElementById('drawer-overlay'); | |
| if (!drawer || !overlay) return; | |
| if (show) { | |
| drawer.classList.remove('drawer-closed'); drawer.classList.add('drawer-open'); overlay.classList.remove('hidden'); fetchHistory(); | |
| } else { | |
| drawer.classList.remove('drawer-open'); drawer.classList.add('drawer-closed'); overlay.classList.add('hidden'); | |
| } | |
| } | |
| let currentVideoAPath = "", currentVideoCPath = ""; | |
| function savePreference(key, value) { localStorage.setItem('continuity_' + key, value); } | |
| function loadPreferences() { | |
| const s = localStorage.getItem('continuity_style'); | |
| const a = localStorage.getItem('continuity_audio'); | |
| if (s) document.getElementById('style-select').value = s; | |
| if (a) document.getElementById('audio-input').value = a; | |
| } | |
| loadPreferences(); | |
| async function fetchHistory() { | |
| const c = document.getElementById('gallery-content'); | |
| c.innerHTML = '<div class="text-center mt-10"><span class="material-symbols-outlined animate-spin">progress_activity</span></div>'; | |
| try { | |
| const res = await fetch('/history'); | |
| const data = await res.json(); | |
| if (!data || !data.length) { c.innerHTML = '<div class="text-center text-gray-500 mt-10 text-xs">No history found.</div>'; return; } | |
| c.innerHTML = data.map(i => `<div class="bg-black/40 rounded-lg overflow-hidden border border-white/5 mb-4 group"><video src="${i.url}" class="w-full aspect-video object-cover" controls></video><div class="p-2 flex justify-between items-center bg-white/5"><span class="text-[10px] text-gray-400 truncate w-32">${i.name}</span><a href="${i.url}" download class="text-gray-400 hover:text-white transition-colors"><span class="material-symbols-outlined text-sm">download</span></a></div></div>`).join(''); | |
| } catch (e) { c.innerHTML = '<div class="text-center text-red-400 mt-10">Error loading history.</div>'; } | |
| } | |
| function handleFileSelect(input, labelId) { | |
| if (input.files[0]) { | |
| document.getElementById(labelId).innerText = input.files[0].name; | |
| document.getElementById(labelId).classList.add("text-primary", "font-bold"); | |
| } | |
| } | |
| function resetUI() { | |
| document.getElementById("analysis-panel").classList.remove("hidden"); | |
| document.getElementById("review-panel").classList.add("hidden"); | |
| document.getElementById("prompt-box").value = ""; | |
| document.getElementById("analysis-a-text").innerText = "Waiting for analysis..."; | |
| document.getElementById("analysis-c-text").innerText = "Waiting for analysis..."; | |
| currentVideoAPath = ""; currentVideoCPath = ""; | |
| document.getElementById("bridge-content").innerHTML = `<div class="absolute inset-0 bg-cover bg-center opacity-20" style="background-image:url('https://images.unsplash.com/photo-1614850523060-8da1d56ae167')"></div><div class="absolute inset-0 flex flex-col items-center justify-center"><span class="material-symbols-outlined text-3xl text-primary mb-2">auto_awesome</span><p class="text-xs text-gray-400">Ready</p></div>`; | |
| document.getElementById("bridge-card-outer").classList.replace("border-primary", "border-primary/20"); | |
| document.getElementById("bridge-border").classList.replace("border-primary/50", "border-transparent"); | |
| document.getElementById("merged-download-container").classList.add("hidden"); | |
| document.getElementById("panel-download-btn").classList.add("hidden"); | |
| } | |
| document.getElementById("analyze-btn").addEventListener("click", async () => { | |
| const fA = document.getElementById("video-upload-a").files[0], fC = document.getElementById("video-upload-c").files[0]; | |
| if (!fA || !fC) return alert("Upload both scenes."); | |
| const btn = document.getElementById("analyze-btn"); | |
| btn.disabled = true; btn.innerHTML = `Analyzing...`; | |
| const fd = new FormData(); fd.append("video_a", fA); fd.append("video_c", fC); | |
| try { | |
| const res = await fetch("/analyze", { method: "POST", body: fd }); | |
| const data = await res.json(); | |
| document.getElementById("prompt-box").value = data.prompt; | |
| document.getElementById("analysis-a-text").innerText = data.analysis_a || "No details."; | |
| document.getElementById("analysis-c-text").innerText = data.analysis_c || "No details."; | |
| currentVideoAPath = data.video_a_path; currentVideoCPath = data.video_c_path; | |
| document.getElementById("analysis-panel").classList.add("hidden"); | |
| document.getElementById("review-panel").classList.remove("hidden"); | |
| } catch (e) { alert(e.message); } finally { btn.disabled = false; btn.innerHTML = `<span class="material-symbols-outlined text-lg">analytics</span> Analyze Scenes`; } | |
| }); | |
| document.getElementById("generate-btn").addEventListener("click", async () => { | |
| const btn = document.getElementById("generate-btn"); | |
| btn.disabled = true; btn.innerHTML = `Generating...`; | |
| try { | |
| const res = await fetch("/generate", { | |
| method: "POST", headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| prompt: document.getElementById("prompt-box").value, | |
| style: document.getElementById("style-select").value, | |
| audio_prompt: document.getElementById("audio-input").value, | |
| negative_prompt: document.getElementById("negative-prompt").value, | |
| guidance_scale: document.getElementById("guidance-scale").value, | |
| motion_strength: document.getElementById("motion-strength").value, | |
| video_a_path: currentVideoAPath, video_c_path: currentVideoCPath | |
| }) | |
| }); | |
| const data = await res.json(); | |
| const pollStartTime = Date.now(); | |
| const poll = setInterval(async () => { | |
| if (Date.now() - pollStartTime > 600000) { // 10 min timeout | |
| clearInterval(poll); alert("Timeout."); btn.disabled = false; btn.innerHTML = "Timeout"; return; | |
| } | |
| try { | |
| const sRes = await fetch(`/status/${data.job_id}?t=${Date.now()}`); | |
| if (sRes.ok) { | |
| const s = await sRes.json(); | |
| if (s.status === "completed") { | |
| clearInterval(poll); | |
| document.getElementById("bridge-content").innerHTML = `<video controls autoplay loop class="w-full h-full object-contain bg-black"><source src="${s.video_url}" type="video/mp4"></video>`; | |
| document.getElementById("bridge-card-outer").classList.replace("border-primary/20", "border-primary"); | |
| if (s.merged_video_url) { | |
| const dlBtn = document.getElementById("merged-download-btn"); | |
| dlBtn.href = s.merged_video_url; | |
| document.getElementById("merged-download-container").classList.remove("hidden"); | |
| // Show panel button | |
| const panelBtn = document.getElementById("panel-download-btn"); | |
| panelBtn.href = s.merged_video_url; | |
| panelBtn.classList.remove("hidden"); | |
| } | |
| btn.innerHTML = "Done!"; setTimeout(() => { btn.disabled = false; btn.innerHTML = `<span class="material-symbols-outlined">auto_fix_high</span> Generate Video`; }, 3000); | |
| } else if (s.status === "error") { clearInterval(poll); alert(s.log); btn.disabled = false; btn.innerHTML = "Try Again"; } | |
| else { btn.innerHTML = `${s.log} (${s.progress}%)`; } | |
| } | |
| } catch (e) { console.error(e); } | |
| }, 2000); | |
| } catch (e) { alert(e.message); btn.disabled = false; } | |
| }); | |
| </script> | |
| </body> | |
| </html> |