Gaurav vashistha commited on
Commit
d724849
·
1 Parent(s): 70214e8

Fix UI: scrollbars, panel layout, download button overlap

Browse files
Files changed (1) hide show
  1. stitch_continuity_dashboard/code.html +92 -166
stitch_continuity_dashboard/code.html CHANGED
@@ -46,7 +46,6 @@
46
  overflow: hidden;
47
  }
48
 
49
- /* Drawer Transitions */
50
  #gallery-drawer {
51
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
52
  }
@@ -59,7 +58,6 @@
59
  transform: translateX(100%);
60
  }
61
 
62
- /* Range Sliders */
63
  input[type=range] {
64
  -webkit-appearance: none;
65
  background: transparent;
@@ -83,23 +81,38 @@
83
  background: #362445;
84
  border-radius: 2px;
85
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  </style>
87
  </head>
88
 
89
  <body
90
  class="bg-background-dark font-body text-white h-screen w-screen overflow-hidden flex flex-col selection:bg-primary selection:text-white relative">
91
-
92
- <!-- Header: Logo & Top Controls -->
93
  <header
94
  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">
95
  <div class="flex items-center gap-3">
96
  <div
97
  class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary">
98
- <span class="material-symbols-outlined">movie_filter</span>
99
- </div>
100
  <h1 class="text-xl font-display font-bold tracking-tight">Continuity</h1>
101
  </div>
102
-
103
  <div class="flex items-center gap-3">
104
  <button onclick="toggleDrawer(true)"
105
  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">
@@ -107,13 +120,8 @@
107
  </button>
108
  </div>
109
  </header>
110
-
111
- <!-- Main Stage: Scrollable Content -->
112
- <!-- FIX: Increased padding bottom to pb-[32rem] (~512px) to clear floating controls -->
113
  <main class="flex-1 w-full overflow-y-auto relative flex flex-col items-center pt-8 pb-[32rem]">
114
  <div class="w-full max-w-6xl mx-auto flex items-center justify-center gap-4 md:gap-8 lg:gap-12 px-4">
115
-
116
- <!-- SCENE A -->
117
  <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
118
  <div class="flex justify-between px-1"><span
119
  class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span>
@@ -131,8 +139,6 @@
131
  onchange="handleFileSelect(this, 'label-a')">
132
  </div>
133
  </div>
134
-
135
- <!-- GENERATED BRIDGE -->
136
  <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20">
137
  <div class="flex justify-center px-1"><span
138
  class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated
@@ -158,7 +164,7 @@
158
  </div>
159
  </div>
160
  <div id="merged-download-container"
161
- class="hidden flex justify-center mt-4 animate-in fade-in slide-in-from-top-2">
162
  <a id="merged-download-btn" href="#" download
163
  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">
164
  <span class="material-symbols-outlined group-hover:animate-bounce">movie</span> Download Merged
@@ -166,8 +172,6 @@
166
  </a>
167
  </div>
168
  </div>
169
-
170
- <!-- SCENE C -->
171
  <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
172
  <div class="flex justify-end px-1"><span
173
  class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span></div>
@@ -184,14 +188,9 @@
184
  onchange="handleFileSelect(this, 'label-c')">
185
  </div>
186
  </div>
187
-
188
  </div>
189
  </main>
190
-
191
- <!-- Floating Controls (Absolute/Fixed over content) -->
192
  <div class="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 w-full max-w-2xl px-4 pointer-events-none">
193
-
194
- <!-- Analysis Panel -->
195
  <div id="analysis-panel"
196
  class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2 pointer-events-auto">
197
  <div class="flex flex-col"><span class="text-sm font-bold text-white">Continuity Engine</span><span
@@ -205,8 +204,6 @@
205
  class="material-symbols-outlined text-lg">analytics</span> Analyze Scenes</button>
206
  </div>
207
  </div>
208
-
209
- <!-- Review Panel -->
210
  <div id="review-panel"
211
  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">
212
  <div class="flex items-center justify-between border-b border-white/10 pb-3">
@@ -221,84 +218,94 @@
221
  class="text-xs text-gray-500 hover:text-white uppercase tracking-wider">Reset</button>
222
  </div>
223
  </div>
224
-
225
- <!-- NEW: Structured Analysis Display -->
226
  <div class="grid grid-cols-2 gap-4 mb-2">
227
- <div class="bg-white/5 p-2 rounded-lg border border-white/10">
228
- <span class="text-[9px] font-bold text-primary uppercase">Scene A Analysis</span>
229
- <p id="analysis-a-text" class="text-[10px] text-gray-300 h-10 overflow-y-auto mt-1 leading-tight">
 
 
230
  Waiting for analysis...</p>
231
  </div>
232
- <div class="bg-white/5 p-2 rounded-lg border border-white/10">
233
- <span class="text-[9px] font-bold text-primary uppercase">Scene C Analysis</span>
234
- <p id="analysis-c-text" class="text-[10px] text-gray-300 h-10 overflow-y-auto mt-1 leading-tight">
 
 
235
  Waiting for analysis...</p>
236
  </div>
237
  </div>
238
-
239
  <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
240
- Direction (Bridge B)</label><textarea id="prompt-box" rows="3"
241
- 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"></textarea>
 
242
  </div>
 
243
  <div class="grid grid-cols-2 gap-4">
244
  <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
245
- Style</label><select id="style-select" onchange="savePreference('style', this.value)"
 
246
  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">
247
  <option value="Cinematic">Cinematic</option>
248
  <option value="Anime">Anime</option>
249
  <option value="Cyberpunk">Cyberpunk</option>
250
  <option value="VHS">VHS Glitch</option>
251
  <option value="Noir">Noir</option>
252
- </select></div>
 
253
  <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio
254
- Mood</label><select id="audio-input" onchange="savePreference('audio', this.value)"
 
255
  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">
256
  <option value="Cinematic orchestral score">Cinematic</option>
257
  <option value="Industrial synthwave">Cyberpunk</option>
258
  <option value="Nature sounds">Nature</option>
259
  <option value="Tense atmosphere">Horror</option>
260
  <option value="High energy rock">Action</option>
261
- </select></div>
 
262
  </div>
263
  <div class="border-t border-white/10 pt-3">
264
  <button onclick="document.getElementById('advanced-settings').classList.toggle('hidden')"
265
- class="flex items-center gap-2 text-xs font-bold text-gray-400 uppercase tracking-widest hover:text-white transition-colors w-full"><span
266
- class="material-symbols-outlined text-sm">tune</span> Advanced Physics & Controls <span
267
- class="material-symbols-outlined text-sm ml-auto">expand_more</span></button>
 
268
  <div id="advanced-settings"
269
  class="hidden pt-3 grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in slide-in-from-top-2">
270
  <div class="col-span-1 md:col-span-2"><label
271
- class="text-[10px] text-gray-500 uppercase font-bold">Negative Prompt</label><input
272
- id="negative-prompt" type="text" placeholder="text, blurry, watermark"
273
  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">
274
  </div>
275
  <div>
276
  <div class="flex justify-between"><label
277
  class="text-[10px] text-gray-500 uppercase font-bold">Guidance Scale</label><span
278
- id="guidance-val" class="text-[10px] text-primary">5.0</span></div><input
279
- id="guidance-scale" type="range" min="1" max="20" value="5" step="0.5" class="w-full mt-2"
 
280
  oninput="document.getElementById('guidance-val').innerText = this.value">
281
  </div>
282
  <div>
283
  <div class="flex justify-between"><label
284
  class="text-[10px] text-gray-500 uppercase font-bold">Motion Strength</label><span
285
- id="motion-val" class="text-[10px] text-primary">5</span></div><input
286
- id="motion-strength" type="range" min="1" max="10" value="5" class="w-full mt-2"
287
  oninput="document.getElementById('motion-val').innerText = this.value">
288
  </div>
289
  </div>
290
  </div>
 
 
 
 
291
  <button id="generate-btn"
292
- 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
293
- class="material-symbols-outlined">auto_fix_high</span> Generate Video</button>
 
294
  </div>
295
  </div>
296
-
297
- <!-- History Drawer Overlay -->
298
  <div id="drawer-overlay" onclick="toggleDrawer(false)"
299
  class="fixed inset-0 bg-black/80 backdrop-blur-sm z-[90] hidden transition-opacity"></div>
300
-
301
- <!-- History Drawer -->
302
  <aside id="gallery-drawer"
303
  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">
304
  <div class="p-6 border-b border-white/5 flex items-center justify-between bg-surface-dark">
@@ -312,33 +319,18 @@
312
  <div class="text-center text-gray-500 mt-10">Loading history...</div>
313
  </div>
314
  </aside>
315
-
316
  <script>
317
  function toggleDrawer(show) {
318
  const drawer = document.getElementById('gallery-drawer');
319
  const overlay = document.getElementById('drawer-overlay');
320
-
321
- if (!drawer || !overlay) {
322
- console.error("Drawer elements not found! Check HTML structure.");
323
- return;
324
- }
325
-
326
  if (show) {
327
- drawer.classList.remove('drawer-closed');
328
- drawer.classList.add('drawer-open');
329
- overlay.classList.remove('hidden');
330
- fetchHistory();
331
  } else {
332
- drawer.classList.remove('drawer-open');
333
- drawer.classList.add('drawer-closed');
334
- overlay.classList.add('hidden');
335
  }
336
  }
337
-
338
- // --- APP LOGIC ---
339
- let currentVideoAPath = "";
340
- let currentVideoCPath = "";
341
-
342
  function savePreference(key, value) { localStorage.setItem('continuity_' + key, value); }
343
  function loadPreferences() {
344
  const s = localStorage.getItem('continuity_style');
@@ -347,96 +339,58 @@
347
  if (a) document.getElementById('audio-input').value = a;
348
  }
349
  loadPreferences();
350
-
351
  async function fetchHistory() {
352
  const c = document.getElementById('gallery-content');
353
  c.innerHTML = '<div class="text-center mt-10"><span class="material-symbols-outlined animate-spin">progress_activity</span></div>';
354
  try {
355
  const res = await fetch('/history');
356
- if (!res.ok) throw new Error(res.statusText);
357
  const data = await res.json();
358
- if (!data || !data.length) {
359
- c.innerHTML = '<div class="text-center text-gray-500 mt-10 text-xs">No history found.</div>';
360
- return;
361
- }
362
- c.innerHTML = data.map(i => `
363
- <div class="bg-black/40 rounded-lg overflow-hidden border border-white/5 mb-4 group">
364
- <video src="${i.url}" class="w-full aspect-video object-cover" controls></video>
365
- <div class="p-2 flex justify-between items-center bg-white/5">
366
- <span class="text-[10px] text-gray-400 truncate w-32">${i.name}</span>
367
- <a href="${i.url}" download class="text-gray-400 hover:text-white transition-colors"><span class="material-symbols-outlined text-sm">download</span></a>
368
- </div>
369
- </div>`).join('');
370
- } catch (e) {
371
- c.innerHTML = '<div class="text-center text-red-400 mt-10">Error loading history.</div>';
372
- }
373
  }
374
-
375
  function handleFileSelect(input, labelId) {
376
  if (input.files[0]) {
377
  document.getElementById(labelId).innerText = input.files[0].name;
378
  document.getElementById(labelId).classList.add("text-primary", "font-bold");
379
  }
380
  }
381
-
382
  function resetUI() {
383
  document.getElementById("analysis-panel").classList.remove("hidden");
384
  document.getElementById("review-panel").classList.add("hidden");
385
  document.getElementById("prompt-box").value = "";
386
-
387
- // Reset Analysis Fields
388
  document.getElementById("analysis-a-text").innerText = "Waiting for analysis...";
389
  document.getElementById("analysis-c-text").innerText = "Waiting for analysis...";
390
-
391
- currentVideoAPath = "";
392
- currentVideoCPath = "";
393
-
394
  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>`;
395
  document.getElementById("bridge-card-outer").classList.replace("border-primary", "border-primary/20");
396
  document.getElementById("bridge-border").classList.replace("border-primary/50", "border-transparent");
397
  document.getElementById("merged-download-container").classList.add("hidden");
 
398
  }
399
-
400
  document.getElementById("analyze-btn").addEventListener("click", async () => {
401
- const fA = document.getElementById("video-upload-a").files[0];
402
- const fC = document.getElementById("video-upload-c").files[0];
403
  if (!fA || !fC) return alert("Upload both scenes.");
404
  const btn = document.getElementById("analyze-btn");
405
- btn.disabled = true;
406
- btn.innerHTML = `Analyzing...`;
407
- const fd = new FormData();
408
- fd.append("video_a", fA);
409
- fd.append("video_c", fC);
410
  try {
411
  const res = await fetch("/analyze", { method: "POST", body: fd });
412
- if (!res.ok) throw new Error(res.statusText);
413
  const data = await res.json();
414
  document.getElementById("prompt-box").value = data.prompt;
415
-
416
- // Populate Analysis Fields
417
- document.getElementById("analysis-a-text").innerText = data.analysis_a || "No details found.";
418
- document.getElementById("analysis-c-text").innerText = data.analysis_c || "No details found.";
419
-
420
- currentVideoAPath = data.video_a_path;
421
- currentVideoCPath = data.video_c_path;
422
  document.getElementById("analysis-panel").classList.add("hidden");
423
  document.getElementById("review-panel").classList.remove("hidden");
424
- } catch (e) {
425
- alert(e.message);
426
- } finally {
427
- btn.disabled = false;
428
- btn.innerHTML = `<span class="material-symbols-outlined text-lg">analytics</span> Analyze Scenes`;
429
- }
430
  });
431
-
432
  document.getElementById("generate-btn").addEventListener("click", async () => {
433
  const btn = document.getElementById("generate-btn");
434
- btn.disabled = true;
435
- btn.innerHTML = `Generating...`;
436
  try {
437
  const res = await fetch("/generate", {
438
- method: "POST",
439
- headers: { "Content-Type": "application/json" },
440
  body: JSON.stringify({
441
  prompt: document.getElementById("prompt-box").value,
442
  style: document.getElementById("style-select").value,
@@ -444,67 +398,39 @@
444
  negative_prompt: document.getElementById("negative-prompt").value,
445
  guidance_scale: document.getElementById("guidance-scale").value,
446
  motion_strength: document.getElementById("motion-strength").value,
447
- video_a_path: currentVideoAPath,
448
- video_c_path: currentVideoCPath
449
  })
450
  });
451
- if (!res.ok) throw new Error(res.statusText);
452
  const data = await res.json();
453
  const pollStartTime = Date.now();
454
  const poll = setInterval(async () => {
455
- // DEAD MAN'S SWITCH: 180s timeout
456
- if (Date.now() - pollStartTime > 180000) {
457
- clearInterval(poll);
458
- alert("Timeout: Server took too long (3 min). Job may still be processing in background.");
459
- btn.disabled = false;
460
- btn.innerHTML = `<span class="material-symbols-outlined">error</span> Timeout`;
461
- return;
462
  }
463
  try {
464
  const sRes = await fetch(`/status/${data.job_id}?t=${Date.now()}`);
465
  if (sRes.ok) {
466
- window.fc = 0;
467
  const s = await sRes.json();
468
  if (s.status === "completed") {
469
  clearInterval(poll);
470
  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>`;
471
  document.getElementById("bridge-card-outer").classList.replace("border-primary/20", "border-primary");
472
- document.getElementById("bridge-border").classList.replace("border-primary/50", "border-transparent");
473
  if (s.merged_video_url) {
474
  const dlBtn = document.getElementById("merged-download-btn");
475
  dlBtn.href = s.merged_video_url;
476
  document.getElementById("merged-download-container").classList.remove("hidden");
 
 
 
 
477
  }
478
- btn.innerHTML = "Done!";
479
- setTimeout(() => {
480
- btn.disabled = false;
481
- btn.innerHTML = `<span class="material-symbols-outlined">auto_fix_high</span> Generate Video`;
482
- }, 3000);
483
- } else if (s.status === "error") {
484
- clearInterval(poll);
485
- alert(s.log);
486
- btn.disabled = false;
487
- btn.innerHTML = "Try Again";
488
- } else {
489
- btn.innerHTML = `${s.log} (${s.progress}%)`;
490
- }
491
- } else {
492
- throw new Error("Server Error");
493
  }
494
- } catch (e) {
495
- window.fc = (window.fc || 0) + 1;
496
- if (window.fc > 5) {
497
- clearInterval(poll);
498
- alert("Connection lost. The active job may get lost but you can try refreshing.");
499
- btn.disabled = false;
500
- btn.innerHTML = "Connection Failed";
501
- }
502
- }
503
- }, 1500);
504
- } catch (e) {
505
- alert(e.message);
506
- btn.disabled = false;
507
- }
508
  });
509
  </script>
510
  </body>
 
46
  overflow: hidden;
47
  }
48
 
 
49
  #gallery-drawer {
50
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
51
  }
 
58
  transform: translateX(100%);
59
  }
60
 
 
61
  input[type=range] {
62
  -webkit-appearance: none;
63
  background: transparent;
 
81
  background: #362445;
82
  border-radius: 2px;
83
  }
84
+
85
+ /* Custom Scrollbar */
86
+ .custom-scrollbar::-webkit-scrollbar {
87
+ width: 6px;
88
+ }
89
+
90
+ .custom-scrollbar::-webkit-scrollbar-track {
91
+ background: rgba(255, 255, 255, 0.05);
92
+ border-radius: 4px;
93
+ }
94
+
95
+ .custom-scrollbar::-webkit-scrollbar-thumb {
96
+ background: rgba(127, 13, 242, 0.5);
97
+ border-radius: 4px;
98
+ }
99
+
100
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
101
+ background: rgba(127, 13, 242, 0.8);
102
+ }
103
  </style>
104
  </head>
105
 
106
  <body
107
  class="bg-background-dark font-body text-white h-screen w-screen overflow-hidden flex flex-col selection:bg-primary selection:text-white relative">
 
 
108
  <header
109
  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">
110
  <div class="flex items-center gap-3">
111
  <div
112
  class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary">
113
+ <span class="material-symbols-outlined">movie_filter</span></div>
 
114
  <h1 class="text-xl font-display font-bold tracking-tight">Continuity</h1>
115
  </div>
 
116
  <div class="flex items-center gap-3">
117
  <button onclick="toggleDrawer(true)"
118
  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">
 
120
  </button>
121
  </div>
122
  </header>
 
 
 
123
  <main class="flex-1 w-full overflow-y-auto relative flex flex-col items-center pt-8 pb-[32rem]">
124
  <div class="w-full max-w-6xl mx-auto flex items-center justify-center gap-4 md:gap-8 lg:gap-12 px-4">
 
 
125
  <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
126
  <div class="flex justify-between px-1"><span
127
  class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span>
 
139
  onchange="handleFileSelect(this, 'label-a')">
140
  </div>
141
  </div>
 
 
142
  <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20">
143
  <div class="flex justify-center px-1"><span
144
  class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated
 
164
  </div>
165
  </div>
166
  <div id="merged-download-container"
167
+ class="hidden flex justify-center mt-8 relative z-30 animate-in fade-in slide-in-from-top-2">
168
  <a id="merged-download-btn" href="#" download
169
  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">
170
  <span class="material-symbols-outlined group-hover:animate-bounce">movie</span> Download Merged
 
172
  </a>
173
  </div>
174
  </div>
 
 
175
  <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
176
  <div class="flex justify-end px-1"><span
177
  class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span></div>
 
188
  onchange="handleFileSelect(this, 'label-c')">
189
  </div>
190
  </div>
 
191
  </div>
192
  </main>
 
 
193
  <div class="fixed bottom-8 left-1/2 -translate-x-1/2 z-50 w-full max-w-2xl px-4 pointer-events-none">
 
 
194
  <div id="analysis-panel"
195
  class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2 pointer-events-auto">
196
  <div class="flex flex-col"><span class="text-sm font-bold text-white">Continuity Engine</span><span
 
204
  class="material-symbols-outlined text-lg">analytics</span> Analyze Scenes</button>
205
  </div>
206
  </div>
 
 
207
  <div id="review-panel"
208
  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">
209
  <div class="flex items-center justify-between border-b border-white/10 pb-3">
 
218
  class="text-xs text-gray-500 hover:text-white uppercase tracking-wider">Reset</button>
219
  </div>
220
  </div>
 
 
221
  <div class="grid grid-cols-2 gap-4 mb-2">
222
+ <div class="bg-white/5 p-3 rounded-lg border border-white/10">
223
+ <span class="text-[10px] font-bold text-primary uppercase flex items-center gap-1"><span
224
+ class="material-symbols-outlined text-xs">videocam</span> Scene A Analysis</span>
225
+ <p id="analysis-a-text"
226
+ class="text-[11px] text-gray-300 h-24 overflow-y-auto custom-scrollbar mt-2 leading-relaxed">
227
  Waiting for analysis...</p>
228
  </div>
229
+ <div class="bg-white/5 p-3 rounded-lg border border-white/10">
230
+ <span class="text-[10px] font-bold text-primary uppercase flex items-center gap-1"><span
231
+ class="material-symbols-outlined text-xs">videocam</span> Scene C Analysis</span>
232
+ <p id="analysis-c-text"
233
+ class="text-[11px] text-gray-300 h-24 overflow-y-auto custom-scrollbar mt-2 leading-relaxed">
234
  Waiting for analysis...</p>
235
  </div>
236
  </div>
 
237
  <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
238
+ Direction (Bridge B)</label>
239
+ <textarea id="prompt-box" rows="4"
240
+ 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>
241
  </div>
242
+
243
  <div class="grid grid-cols-2 gap-4">
244
  <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
245
+ Style</label>
246
+ <select id="style-select" onchange="savePreference('style', this.value)"
247
  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">
248
  <option value="Cinematic">Cinematic</option>
249
  <option value="Anime">Anime</option>
250
  <option value="Cyberpunk">Cyberpunk</option>
251
  <option value="VHS">VHS Glitch</option>
252
  <option value="Noir">Noir</option>
253
+ </select>
254
+ </div>
255
  <div><label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio
256
+ Mood</label>
257
+ <select id="audio-input" onchange="savePreference('audio', this.value)"
258
  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">
259
  <option value="Cinematic orchestral score">Cinematic</option>
260
  <option value="Industrial synthwave">Cyberpunk</option>
261
  <option value="Nature sounds">Nature</option>
262
  <option value="Tense atmosphere">Horror</option>
263
  <option value="High energy rock">Action</option>
264
+ </select>
265
+ </div>
266
  </div>
267
  <div class="border-t border-white/10 pt-3">
268
  <button onclick="document.getElementById('advanced-settings').classList.toggle('hidden')"
269
+ class="flex items-center gap-2 text-xs font-bold text-gray-400 uppercase tracking-widest hover:text-white transition-colors w-full">
270
+ <span class="material-symbols-outlined text-sm">tune</span> Advanced Physics & Controls <span
271
+ class="material-symbols-outlined text-sm ml-auto">expand_more</span>
272
+ </button>
273
  <div id="advanced-settings"
274
  class="hidden pt-3 grid grid-cols-1 md:grid-cols-2 gap-4 animate-in fade-in slide-in-from-top-2">
275
  <div class="col-span-1 md:col-span-2"><label
276
+ class="text-[10px] text-gray-500 uppercase font-bold">Negative Prompt</label>
277
+ <input id="negative-prompt" type="text" placeholder="text, blurry, watermark"
278
  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">
279
  </div>
280
  <div>
281
  <div class="flex justify-between"><label
282
  class="text-[10px] text-gray-500 uppercase font-bold">Guidance Scale</label><span
283
+ id="guidance-val" class="text-[10px] text-primary">5.0</span></div>
284
+ <input id="guidance-scale" type="range" min="1" max="20" value="5" step="0.5"
285
+ class="w-full mt-2"
286
  oninput="document.getElementById('guidance-val').innerText = this.value">
287
  </div>
288
  <div>
289
  <div class="flex justify-between"><label
290
  class="text-[10px] text-gray-500 uppercase font-bold">Motion Strength</label><span
291
+ id="motion-val" class="text-[10px] text-primary">5</span></div>
292
+ <input id="motion-strength" type="range" min="1" max="10" value="5" class="w-full mt-2"
293
  oninput="document.getElementById('motion-val').innerText = this.value">
294
  </div>
295
  </div>
296
  </div>
297
+ <a id="panel-download-btn" href="#" download
298
+ 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">
299
+ <span class="material-symbols-outlined">download</span> Download Final Video
300
+ </a>
301
  <button id="generate-btn"
302
+ 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">
303
+ <span class="material-symbols-outlined">auto_fix_high</span> Generate Video
304
+ </button>
305
  </div>
306
  </div>
 
 
307
  <div id="drawer-overlay" onclick="toggleDrawer(false)"
308
  class="fixed inset-0 bg-black/80 backdrop-blur-sm z-[90] hidden transition-opacity"></div>
 
 
309
  <aside id="gallery-drawer"
310
  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">
311
  <div class="p-6 border-b border-white/5 flex items-center justify-between bg-surface-dark">
 
319
  <div class="text-center text-gray-500 mt-10">Loading history...</div>
320
  </div>
321
  </aside>
 
322
  <script>
323
  function toggleDrawer(show) {
324
  const drawer = document.getElementById('gallery-drawer');
325
  const overlay = document.getElementById('drawer-overlay');
326
+ if (!drawer || !overlay) return;
 
 
 
 
 
327
  if (show) {
328
+ drawer.classList.remove('drawer-closed'); drawer.classList.add('drawer-open'); overlay.classList.remove('hidden'); fetchHistory();
 
 
 
329
  } else {
330
+ drawer.classList.remove('drawer-open'); drawer.classList.add('drawer-closed'); overlay.classList.add('hidden');
 
 
331
  }
332
  }
333
+ let currentVideoAPath = "", currentVideoCPath = "";
 
 
 
 
334
  function savePreference(key, value) { localStorage.setItem('continuity_' + key, value); }
335
  function loadPreferences() {
336
  const s = localStorage.getItem('continuity_style');
 
339
  if (a) document.getElementById('audio-input').value = a;
340
  }
341
  loadPreferences();
 
342
  async function fetchHistory() {
343
  const c = document.getElementById('gallery-content');
344
  c.innerHTML = '<div class="text-center mt-10"><span class="material-symbols-outlined animate-spin">progress_activity</span></div>';
345
  try {
346
  const res = await fetch('/history');
 
347
  const data = await res.json();
348
+ if (!data || !data.length) { c.innerHTML = '<div class="text-center text-gray-500 mt-10 text-xs">No history found.</div>'; return; }
349
+ 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('');
350
+ } catch (e) { c.innerHTML = '<div class="text-center text-red-400 mt-10">Error loading history.</div>'; }
 
 
 
 
 
 
 
 
 
 
 
 
351
  }
 
352
  function handleFileSelect(input, labelId) {
353
  if (input.files[0]) {
354
  document.getElementById(labelId).innerText = input.files[0].name;
355
  document.getElementById(labelId).classList.add("text-primary", "font-bold");
356
  }
357
  }
 
358
  function resetUI() {
359
  document.getElementById("analysis-panel").classList.remove("hidden");
360
  document.getElementById("review-panel").classList.add("hidden");
361
  document.getElementById("prompt-box").value = "";
 
 
362
  document.getElementById("analysis-a-text").innerText = "Waiting for analysis...";
363
  document.getElementById("analysis-c-text").innerText = "Waiting for analysis...";
364
+ currentVideoAPath = ""; currentVideoCPath = "";
 
 
 
365
  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>`;
366
  document.getElementById("bridge-card-outer").classList.replace("border-primary", "border-primary/20");
367
  document.getElementById("bridge-border").classList.replace("border-primary/50", "border-transparent");
368
  document.getElementById("merged-download-container").classList.add("hidden");
369
+ document.getElementById("panel-download-btn").classList.add("hidden");
370
  }
 
371
  document.getElementById("analyze-btn").addEventListener("click", async () => {
372
+ const fA = document.getElementById("video-upload-a").files[0], fC = document.getElementById("video-upload-c").files[0];
 
373
  if (!fA || !fC) return alert("Upload both scenes.");
374
  const btn = document.getElementById("analyze-btn");
375
+ btn.disabled = true; btn.innerHTML = `Analyzing...`;
376
+ const fd = new FormData(); fd.append("video_a", fA); fd.append("video_c", fC);
 
 
 
377
  try {
378
  const res = await fetch("/analyze", { method: "POST", body: fd });
 
379
  const data = await res.json();
380
  document.getElementById("prompt-box").value = data.prompt;
381
+ document.getElementById("analysis-a-text").innerText = data.analysis_a || "No details.";
382
+ document.getElementById("analysis-c-text").innerText = data.analysis_c || "No details.";
383
+ currentVideoAPath = data.video_a_path; currentVideoCPath = data.video_c_path;
 
 
 
 
384
  document.getElementById("analysis-panel").classList.add("hidden");
385
  document.getElementById("review-panel").classList.remove("hidden");
386
+ } catch (e) { alert(e.message); } finally { btn.disabled = false; btn.innerHTML = `<span class="material-symbols-outlined text-lg">analytics</span> Analyze Scenes`; }
 
 
 
 
 
387
  });
 
388
  document.getElementById("generate-btn").addEventListener("click", async () => {
389
  const btn = document.getElementById("generate-btn");
390
+ btn.disabled = true; btn.innerHTML = `Generating...`;
 
391
  try {
392
  const res = await fetch("/generate", {
393
+ method: "POST", headers: { "Content-Type": "application/json" },
 
394
  body: JSON.stringify({
395
  prompt: document.getElementById("prompt-box").value,
396
  style: document.getElementById("style-select").value,
 
398
  negative_prompt: document.getElementById("negative-prompt").value,
399
  guidance_scale: document.getElementById("guidance-scale").value,
400
  motion_strength: document.getElementById("motion-strength").value,
401
+ video_a_path: currentVideoAPath, video_c_path: currentVideoCPath
 
402
  })
403
  });
 
404
  const data = await res.json();
405
  const pollStartTime = Date.now();
406
  const poll = setInterval(async () => {
407
+ if (Date.now() - pollStartTime > 600000) { // 10 min timeout
408
+ clearInterval(poll); alert("Timeout."); btn.disabled = false; btn.innerHTML = "Timeout"; return;
 
 
 
 
 
409
  }
410
  try {
411
  const sRes = await fetch(`/status/${data.job_id}?t=${Date.now()}`);
412
  if (sRes.ok) {
 
413
  const s = await sRes.json();
414
  if (s.status === "completed") {
415
  clearInterval(poll);
416
  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>`;
417
  document.getElementById("bridge-card-outer").classList.replace("border-primary/20", "border-primary");
 
418
  if (s.merged_video_url) {
419
  const dlBtn = document.getElementById("merged-download-btn");
420
  dlBtn.href = s.merged_video_url;
421
  document.getElementById("merged-download-container").classList.remove("hidden");
422
+ // Show panel button
423
+ const panelBtn = document.getElementById("panel-download-btn");
424
+ panelBtn.href = s.merged_video_url;
425
+ panelBtn.classList.remove("hidden");
426
  }
427
+ btn.innerHTML = "Done!"; setTimeout(() => { btn.disabled = false; btn.innerHTML = `<span class="material-symbols-outlined">auto_fix_high</span> Generate Video`; }, 3000);
428
+ } else if (s.status === "error") { clearInterval(poll); alert(s.log); btn.disabled = false; btn.innerHTML = "Try Again"; }
429
+ else { btn.innerHTML = `${s.log} (${s.progress}%)`; }
 
 
 
 
 
 
 
 
 
 
 
 
430
  }
431
+ } catch (e) { console.error(e); }
432
+ }, 2000);
433
+ } catch (e) { alert(e.message); btn.disabled = false; }
 
 
 
 
 
 
 
 
 
 
 
434
  });
435
  </script>
436
  </body>