Gaurav vashistha commited on
Commit
f1e9302
·
1 Parent(s): 87e3cb1

fix: restore UI layout and fix floating controls

Browse files
Files changed (1) hide show
  1. stitch_continuity_dashboard/code.html +271 -321
stitch_continuity_dashboard/code.html CHANGED
@@ -1,12 +1,17 @@
1
  <!DOCTYPE html>
2
  <html class="dark" lang="en">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Continuity</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
- <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet" />
 
 
 
 
10
  <script>
11
  tailwind.config = {
12
  darkMode: 'class',
@@ -18,6 +23,7 @@
18
  background: '#0d0612', // Very Dark Purple/Black
19
  surface: '#1c1326', // Dark Purple Surface
20
  'surface-dark': '#130a1a',
 
21
  accent: '#d946ef', // Fuchsia
22
  },
23
  fontFamily: {
@@ -36,384 +42,328 @@
36
  body {
37
  background-color: #0d0612;
38
  background-image: radial-gradient(circle at 15% 50%, rgba(107, 11, 201, 0.15), transparent 25%),
39
- radial-gradient(circle at 85% 30%, rgba(16, 185, 129, 0.1), transparent 25%);
40
  color: #e2e8f0;
 
41
  }
42
-
43
  ::-webkit-scrollbar {
44
- width: 8px;
45
  }
 
46
  ::-webkit-scrollbar-track {
47
- background: #191022;
48
  }
 
49
  ::-webkit-scrollbar-thumb {
50
- background: #4d3168;
51
- border-radius: 4px;
52
  }
 
53
  ::-webkit-scrollbar-thumb:hover {
54
- background: #7f0df2;
55
  }
 
 
56
  .glass-panel {
57
- background: rgba(25, 16, 34, 0.6);
58
- backdrop-filter: blur(12px);
59
- -webkit-backdrop-filter: blur(12px);
60
- border: 1px solid rgba(255, 255, 255, 0.05);
61
- }
62
- /* Footer fade for the floating button */
63
- .footer-fade {
64
- background: linear-gradient(to top, #191022 20%, transparent 100%);
65
- pointer-events: none;
66
- }
67
- .connection-line {
68
- height: 2px;
69
- flex-grow: 1;
70
- background: linear-gradient(90deg, #4d3168 0%, #7f0df2 50%, #4d3168 100%);
71
- opacity: 0.5;
72
- position: relative;
73
- }
74
- .connection-line::after {
75
- content: '';
76
- position: absolute;
77
- right: 0;
78
- top: 50%;
79
- transform: translateY(-50%);
80
- width: 6px;
81
- height: 6px;
82
- background: #7f0df2;
83
- border-radius: 50%;
84
- box-shadow: 0 0 10px #7f0df2;
85
  }
86
  </style>
87
  </head>
88
- <body class="h-screen flex flex-col overflow-hidden">
 
89
 
90
  <!-- Header -->
91
- <header class="h-16 border-b border-white/5 bg-surface/50 backdrop-blur-md flex items-center justify-between px-6 z-20 shrink-0">
92
  <div class="flex items-center gap-3">
93
- <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-lg">
94
- <span class="material-symbols-outlined text-white text-md">all_inclusive</span>
 
95
  </div>
96
- <h1 class="text-xl font-display font-bold tracking-tight text-white">Continuity</h1>
 
97
  </div>
98
-
99
- <div class="flex items-center gap-4">
100
- <div class="hidden md:flex items-center gap-2 px-3 py-1.5 rounded-full bg-white/5 border border-white/5">
101
- <div class="w-2 h-2 rounded-full bg-green-500 animate-pulse"></div>
102
- <span class="text-xs font-medium text-gray-300">System Online</span>
103
  </div>
104
- <a href="https://github.com/Bhishaj9/Continuity" target="_blank" class="p-2 text-gray-400 hover:text-white transition-colors">
105
- <svg viewBox="0 0 24 24" class="w-5 h-5 fill-current" aria-hidden="true"><path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path></svg>
106
- </a>
107
  </div>
108
- </header>
109
-
110
- <!-- Main Workspace -->
111
- <main class="flex-1 flex overflow-hidden">
112
-
113
- <!-- Left Panel: Scene Inputs (Scrollable) -->
114
- <div class="w-1/3 min-w-[320px] max-w-[400px] border-r border-white/5 bg-surface/30 flex flex-col h-full">
115
- <div class="p-6 overflow-y-auto custom-scrollbar flex-1 pb-32">
116
- <h2 class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-6">Scene Setup</h2>
117
-
118
- <!-- Scene A Upload -->
119
- <div class="mb-8 group">
120
- <div class="flex items-center justify-between mb-2">
121
- <label class="text-white font-medium text-sm">Scene A (Start)</label>
122
- <span class="text-[10px] px-2 py-0.5 rounded bg-primary/20 text-primary border border-primary/30">Input</span>
123
- </div>
124
- <div class="relative w-full aspect-video rounded-xl bg-surface-dark border-2 border-dashed border-white/10 group-hover:border-primary/50 transition-colors overflow-hidden flex flex-col items-center justify-center cursor-pointer hover:bg-white/5" onclick="document.getElementById('video-upload-a').click()">
125
- <span class="material-symbols-outlined text-4xl text-gray-600 mb-2 group-hover:text-primary transition-colors">movie</span>
126
- <span id="label-a" class="text-xs text-gray-500 font-medium">Click to Upload Video A</span>
127
- <input type="file" id="video-upload-a" class="hidden" accept="video/*" onchange="handleFileSelect(this, 'label-a')">
128
  </div>
 
 
 
129
  </div>
 
130
 
131
- <!-- Connection Arrow -->
132
- <div class="flex items-center justify-center mb-8 opacity-50">
133
- <div class="h-12 w-[1px] bg-gradient-to-b from-transparent via-primary to-transparent"></div>
 
 
134
  </div>
135
-
136
- <!-- Scene C Upload -->
137
- <div class="mb-4 group">
138
- <div class="flex items-center justify-between mb-2">
139
- <label class="text-white font-medium text-sm">Scene C (End)</label>
140
- <span class="text-[10px] px-2 py-0.5 rounded bg-primary/20 text-primary border border-primary/30">Input</span>
141
  </div>
142
- <div class="relative w-full aspect-video rounded-xl bg-surface-dark border-2 border-dashed border-white/10 group-hover:border-primary/50 transition-colors overflow-hidden flex flex-col items-center justify-center cursor-pointer hover:bg-white/5" onclick="document.getElementById('video-upload-c').click()">
143
- <span class="material-symbols-outlined text-4xl text-gray-600 mb-2 group-hover:text-primary transition-colors">movie</span>
144
- <span id="label-c" class="text-xs text-gray-500 font-medium">Click to Upload Video C</span>
145
- <input type="file" id="video-upload-c" class="hidden" accept="video/*" onchange="handleFileSelect(this, 'label-c')">
 
 
146
  </div>
147
  </div>
148
  </div>
149
-
150
- <!-- Bottom Action Area (Fixed) -->
151
- <div class="p-6 border-t border-white/5 bg-surface/50 backdrop-blur-md absolute bottom-0 left-0 w-full z-10">
152
- <!-- 1. Analyze Panel -->
153
- <div id="analysis-panel" class="glass-panel rounded-full p-2 pl-6 flex items-center justify-center shadow-neon">
154
- <button id="analyze-btn"
155
- class="flex items-center gap-2 bg-primary hover:bg-[#6b0bc9] text-white px-8 py-3 rounded-full font-bold text-sm transition-all shadow-[0_0_15px_rgba(127,13,242,0.4)] hover:shadow-[0_0_25px_rgba(127,13,242,0.6)] whitespace-nowrap">
156
- <span class="material-symbols-outlined text-[20px]">analytics</span>
157
- Upload & Analyze Scenes
158
- </button>
159
- </div>
160
-
161
- <!-- 2. Review Panel (Hidden Initially) -->
162
- <div id="review-panel" class="hidden glass-panel rounded-2xl p-4 flex flex-col gap-4 shadow-neon w-full">
163
- <div class="flex items-center justify-between pb-2 border-b border-white/10">
164
- <h3 class="text-white font-display font-bold">🎬 Director's Cut: Review Prompt</h3>
165
- <button onclick="resetUI()" class="text-gray-400 hover:text-white text-xs uppercase tracking-widest">Reset</button>
166
- </div>
167
-
168
- <textarea id="prompt-box" rows="3"
169
- class="w-full bg-surface-dark/50 border border-white/10 rounded-lg p-3 text-white text-sm focus:border-primary focus:ring-1 focus:ring-primary outline-none"
170
- placeholder="AI generated transition prompt will appear here..."></textarea>
171
-
172
- <div class="grid grid-cols-2 gap-4">
173
- <div class="flex flex-col gap-2">
174
- <label for="style-select" class="text-xs font-bold text-gray-400 uppercase tracking-widest pl-1">Visual Style</label>
175
- <select id="style-select" class="w-full bg-surface-dark/50 border border-white/10 rounded-lg p-3 text-white text-sm focus:border-primary focus:ring-1 focus:ring-primary outline-none">
176
- <option value="Cinematic">Cinematic (Default)</option>
177
- <option value="Anime">Anime</option>
178
- <option value="Cyberpunk">Cyberpunk</option>
179
- <option value="VHS Glitch">VHS Glitch</option>
180
- <option value="Claymation">Claymation</option>
181
- <option value="Noir">Noir</option>
182
- </select>
183
- </div>
184
-
185
- <div class="flex flex-col gap-2">
186
- <label for="audio-input" class="text-xs font-bold text-gray-400 uppercase tracking-widest pl-1">Audio Mood</label>
187
- <input id="audio-input" type="text"
188
- class="w-full bg-surface-dark/50 border border-white/10 rounded-lg p-3 text-white text-sm focus:border-primary focus:ring-1 focus:ring-primary outline-none"
189
- placeholder="e.g. Epic orchestral, Cyberpunk synth..." value="Cinematic ambient sound">
190
- </div>
191
- </div>
192
-
193
- <button id="generate-btn"
194
- class="w-full flex items-center justify-center gap-2 bg-gradient-to-r from-primary to-purple-600 hover:from-[#6b0bc9] hover:to-purple-700 text-white px-6 py-3 rounded-xl font-bold text-lg transition-all shadow-lg">
195
- <span class="material-symbols-outlined text-[24px]">movie_filter</span>
196
- ACTION! (Generate Video)
197
- </button>
198
- </div>
199
  </div>
200
  </div>
 
201
 
202
- <!-- Right Panel: The Bridge (Output) -->
203
- <div class="flex-1 bg-[#050308] relative flex flex-col items-center justify-center p-12">
204
-
205
- <!-- Background Decorations -->
206
- <div class="absolute inset-0 overflow-hidden pointer-events-none">
207
- <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-primary/5 rounded-full blur-3xl"></div>
208
- <div class="absolute grid grid-cols-[repeat(20,minmax(0,1fr))] w-full h-full opacity-10">
209
- <!-- Grid lines generated via script or just consistent bg -->
210
- </div>
211
  </div>
 
 
 
 
 
 
212
 
213
- <!-- Main Content Card -->
214
- <div class="relative z-10 w-full max-w-4xl flex flex-col items-center">
215
-
216
- <h2 class="text-center font-display text-4xl font-bold text-white mb-2 tracking-tight">The Bridge</h2>
217
- <p class="text-center text-gray-400 mb-10 max-w-lg">AI-generated seamless transition between Scene A and Scene C.</p>
218
-
219
- <div class="w-full aspect-video rounded-3xl bg-surface/40 border border-white/5 shadow-2xl backdrop-blur-sm p-4 relative flex items-center justify-center group" id="bridge-card">
220
- <!-- Placeholder State -->
221
- <div class="text-center">
222
- <div class="w-20 h-20 rounded-full bg-white/5 flex items-center justify-center mb-4 mx-auto group-hover:scale-110 transition-transform duration-500">
223
- <span class="material-symbols-outlined text-4xl text-gray-600">movie_edit</span>
224
- </div>
225
- <p class="text-gray-500 font-medium">Waiting for inputs...</p>
226
- </div>
227
-
228
- <!-- Loading Overlay (Hidden by default) -->
229
- <div id="loader" class="hidden absolute inset-0 bg-black/80 backdrop-blur-md rounded-2xl flex flex-col items-center justify-center z-20">
230
- <div class="w-16 h-16 border-4 border-primary border-t-transparent rounded-full animate-spin mb-4"></div>
231
- <p class="text-white font-medium animate-pulse">Forging the bridge...</p>
232
- </div>
233
- </div>
234
 
235
- <!-- Timeline / Visualization (Static for now) -->
236
- <div class="w-full mt-8 flex items-center justify-between px-4 opacity-50">
237
- <span class="text-xs font-mono text-gray-500">00:00</span>
238
- <div class="connection-line mx-4"></div>
239
- <span class="text-xs font-mono text-gray-500">00:05</span>
240
- </div>
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  </div>
 
 
 
 
 
 
243
  </div>
244
- </main>
245
 
 
 
 
246
  <script>
247
  let currentVideoAPath = "";
248
  let currentVideoCPath = "";
249
 
250
- // UI Helper: Update filename text when file is selected
251
  function handleFileSelect(input, labelId) {
252
  if (input.files && input.files[0]) {
253
  const label = document.getElementById(labelId);
254
  label.innerText = input.files[0].name;
255
- label.classList.add("text-primary"); // Turn purple to indicate success
256
  }
257
  }
258
-
259
  function resetUI() {
260
- document.getElementById("analysis-panel").classList.remove("hidden");
261
- document.getElementById("review-panel").classList.add("hidden");
262
- document.getElementById("prompt-box").value = "";
263
- currentVideoAPath = "";
264
- currentVideoCPath = "";
265
  }
266
 
267
- // Step 1: Analyze
268
- const analyzeBtn = document.getElementById("analyze-btn");
269
- if (analyzeBtn) {
270
- analyzeBtn.addEventListener("click", async () => {
271
- const fileA = document.getElementById("video-upload-a").files[0];
272
- const fileC = document.getElementById("video-upload-c").files[0];
273
- const btn = document.getElementById("analyze-btn");
274
-
275
- // 1. Validation
276
- if (!fileA || !fileC) {
277
- alert("⚠️ Action Required: Please upload both Scene A and Scene C videos.");
278
- return;
279
- }
280
 
281
- // 2. Loading State
282
- const originalContent = btn.innerHTML;
283
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-[20px] mr-2">progress_activity</span> Analyzing Scenes...`;
284
- btn.disabled = true;
285
- btn.classList.add("opacity-70", "cursor-not-allowed");
286
-
287
- const formData = new FormData();
288
- formData.append("video_a", fileA);
289
- formData.append("video_c", fileC);
290
-
291
- try {
292
- // 3. Send to Server
293
- const response = await fetch("/analyze", {
294
- method: "POST",
295
- body: formData
296
- });
297
-
298
- if (!response.ok) throw new Error("Analysis failed.");
299
-
300
- const data = await response.json();
301
-
302
- // 4. Success: Show Review Panel
303
- document.getElementById("prompt-box").value = data.prompt;
304
- currentVideoAPath = data.video_a_path;
305
- currentVideoCPath = data.video_c_path;
306
-
307
- document.getElementById("analysis-panel").classList.add("hidden");
308
- document.getElementById("review-panel").classList.remove("hidden");
309
-
310
- } catch (error) {
311
- console.error(error);
312
- alert("❌ Analysis Failed: " + error.message);
313
- } finally {
314
- btn.innerHTML = originalContent;
315
- btn.disabled = false;
316
- btn.classList.remove("opacity-70", "cursor-not-allowed");
317
- }
318
- });
319
- }
320
 
321
- // Step 2: Generate
322
- const generateBtn = document.getElementById("generate-btn");
323
- if (generateBtn) {
324
- generateBtn.addEventListener("click", async () => {
325
- const prompt = document.getElementById("prompt-box").value;
326
- const style = document.getElementById("style-select").value;
327
- // Capture Audio Prompt
328
- const audioPrompt = document.getElementById("audio-input").value;
329
- const btn = document.getElementById("generate-btn");
330
-
331
- if (!currentVideoAPath || !currentVideoCPath) {
332
- alert("Session lost. Please re-upload videos.");
333
- resetUI();
334
- return;
335
- }
336
 
337
- // Loading State
338
- const originalContent = btn.innerHTML;
339
- btn.disabled = true;
340
- btn.classList.add("opacity-70", "cursor-not-allowed");
341
-
342
- try {
343
- // 1. Start Job
344
- const response = await fetch("/generate", {
345
- method: "POST",
346
- headers: {
347
- "Content-Type": "application/json"
348
- },
349
- body: JSON.stringify({
350
- prompt: prompt,
351
- style: style,
352
- audio_prompt: audioPrompt, // Send to server
353
- video_a_path: currentVideoAPath,
354
- video_c_path: currentVideoCPath
355
- })
356
- });
357
-
358
- if (!response.ok) throw new Error("Generation failed to start.");
359
-
360
- const data = await response.json();
361
- const jobId = data.job_id;
362
-
363
- // 2. Poll for Status
364
- const pollInterval = setInterval(async () => {
365
- try {
366
- // ADDED TIMESTAMP TO PREVENT CACHING
367
- const statusRes = await fetch(`/status/${jobId}?t=${Date.now()}`);
368
- if (!statusRes.ok) return;
369
-
370
- const statusData = await statusRes.json();
371
-
372
- // Update Button Text
373
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-[20px] mr-2">progress_activity</span> ${statusData.log} (${statusData.progress}%)`;
374
-
375
- if (statusData.status === "completed") {
376
- clearInterval(pollInterval);
377
- // Show Video
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  const bridgeCard = document.getElementById("bridge-card");
379
- if (bridgeCard) {
380
- if (statusData.video_url) {
381
- bridgeCard.innerHTML = `
382
- <video controls autoplay loop class="w-full h-full object-cover rounded-2xl border-2 border-primary shadow-neon">
383
- <source src="${statusData.video_url}" type="video/mp4">
384
- Your browser does not support the video tag.
385
- </video>
386
- `;
387
- document.getElementById("analysis-panel").classList.remove("hidden");
388
- document.getElementById("review-panel").classList.add("hidden");
389
- } else {
390
- alert("Video generated but URL missing.");
391
- }
392
- btn.innerHTML = originalContent;
393
- btn.disabled = false;
394
- btn.classList.remove("opacity-70", "cursor-not-allowed");
395
- }
396
- } else if (statusData.status === "error") {
397
- clearInterval(pollInterval);
398
- alert("❌ Generation Error: " + statusData.log);
399
- btn.innerHTML = originalContent;
400
  btn.disabled = false;
401
- btn.classList.remove("opacity-70", "cursor-not-allowed");
 
 
 
402
  }
403
- } catch (e) {
404
- console.error("Polling error", e);
405
  }
406
- }, 1000);
407
-
408
- } catch (error) {
409
- console.error(error);
410
- alert("❌ Request Failed: " + error.message);
411
- btn.innerHTML = originalContent;
412
- btn.disabled = false;
413
- btn.classList.remove("opacity-70", "cursor-not-allowed");
414
- }
415
- });
416
- }
 
417
  </script>
418
  </body>
 
419
  </html>
 
1
  <!DOCTYPE html>
2
  <html class="dark" lang="en">
3
+
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Continuity</title>
8
  <script src="https://cdn.tailwindcss.com"></script>
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap"
11
+ rel="stylesheet">
12
+ <link
13
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
14
+ rel="stylesheet" />
15
  <script>
16
  tailwind.config = {
17
  darkMode: 'class',
 
23
  background: '#0d0612', // Very Dark Purple/Black
24
  surface: '#1c1326', // Dark Purple Surface
25
  'surface-dark': '#130a1a',
26
+ 'border-dark': 'rgba(255,255,255,0.05)',
27
  accent: '#d946ef', // Fuchsia
28
  },
29
  fontFamily: {
 
42
  body {
43
  background-color: #0d0612;
44
  background-image: radial-gradient(circle at 15% 50%, rgba(107, 11, 201, 0.15), transparent 25%),
45
+ radial-gradient(circle at 85% 30%, rgba(16, 185, 129, 0.1), transparent 25%);
46
  color: #e2e8f0;
47
+ overflow-x: hidden;
48
  }
49
+
50
  ::-webkit-scrollbar {
51
+ width: 8px;
52
  }
53
+
54
  ::-webkit-scrollbar-track {
55
+ background: #191022;
56
  }
57
+
58
  ::-webkit-scrollbar-thumb {
59
+ background: #4d3168;
60
+ border-radius: 4px;
61
  }
62
+
63
  ::-webkit-scrollbar-thumb:hover {
64
+ background: #7f0df2;
65
  }
66
+
67
+ /* Floating Panel Glass Effect */
68
  .glass-panel {
69
+ background: rgba(25, 16, 34, 0.85);
70
+ backdrop-filter: blur(16px);
71
+ -webkit-backdrop-filter: blur(16px);
72
+ border: 1px solid rgba(255, 255, 255, 0.08);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
  </style>
75
  </head>
76
+
77
+ <body class="h-screen flex flex-col relative">
78
 
79
  <!-- Header -->
80
+ <div class="h-20 flex items-center justify-between px-8 z-20 shrink-0">
81
  <div class="flex items-center gap-3">
82
+ <div
83
+ class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary">
84
+ <span class="material-symbols-outlined">movie_filter</span>
85
  </div>
86
+ <h1 class="text-xl font-display font-bold tracking-tight">Continuity <span
87
+ class="opacity-50 font-normal text-sm ml-2">v2.1</span></h1>
88
  </div>
89
+ <div class="flex items-center gap-2">
90
+ <div class="flex items-center gap-2 px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
91
+ <div class="size-2 bg-green-500 rounded-full animate-pulse"></div>
92
+ <span class="text-xs font-bold text-green-400 tracking-wide uppercase">System Online</span>
 
93
  </div>
 
 
 
94
  </div>
95
+ </div>
96
+
97
+ <!-- Main Workspace (Center Aligned) -->
98
+ <main class="flex-1 flex flex-col items-center justify-center p-6 pb-32">
99
+ <div class="w-full max-w-6xl flex items-center justify-center gap-4 md:gap-8 lg:gap-12">
100
+
101
+ <!-- Scene A -->
102
+ <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
103
+ <div class="flex justify-between px-1">
104
+ <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span>
105
+ </div>
106
+ <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"
107
+ onclick="document.getElementById('video-upload-a').click()">
108
+ <div
109
+ class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform">
110
+ <span
111
+ class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
 
 
 
112
  </div>
113
+ <p id="label-a" class="text-xs font-medium text-gray-400 text-center px-4">Upload Start Clip</p>
114
+ <input type="file" id="video-upload-a" accept="video/*" class="hidden"
115
+ onchange="handleFileSelect(this, 'label-a')">
116
  </div>
117
+ </div>
118
 
119
+ <!-- Bridge (Center) -->
120
+ <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20">
121
+ <div class="flex justify-center px-1">
122
+ <span class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated
123
+ Bridge</span>
124
  </div>
125
+ <div id="bridge-card"
126
+ class="relative aspect-video bg-black rounded-2xl border border-primary/20 shadow-neon overflow-hidden flex items-center justify-center group">
127
+ <div
128
+ 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">
 
 
129
  </div>
130
+ <div class="text-center relative z-10 p-6">
131
+ <div
132
+ class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mx-auto mb-3">
133
+ <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
134
+ </div>
135
+ <p class="text-sm text-gray-300">Ready to bridge the gap</p>
136
  </div>
137
  </div>
138
  </div>
139
+
140
+ <!-- Scene C -->
141
+ <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
142
+ <div class="flex justify-end px-1">
143
+ <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span>
144
+ </div>
145
+ <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"
146
+ onclick="document.getElementById('video-upload-c').click()">
147
+ <div
148
+ class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform">
149
+ <span
150
+ class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
151
+ </div>
152
+ <p id="label-c" class="text-xs font-medium text-gray-400 text-center px-4">Upload End Clip</p>
153
+ <input type="file" id="video-upload-c" accept="video/*" class="hidden"
154
+ onchange="handleFileSelect(this, 'label-c')">
155
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  </div>
157
  </div>
158
+ </main>
159
 
160
+ <!-- Floating Director's Suite (Fixed Bottom Center) -->
161
+ <div class="fixed bottom-10 left-1/2 transform -translate-x-1/2 z-50 w-full max-w-md px-4">
162
+
163
+ <!-- Analysis Panel -->
164
+ <div id="analysis-panel"
165
+ class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2 transition-all duration-300">
166
+ <div class="flex flex-col">
167
+ <span class="text-sm font-bold text-white">Continuity Engine</span>
168
+ <span class="text-[10px] text-gray-400 uppercase tracking-wide">Ready for analysis</span>
169
  </div>
170
+ <button id="analyze-btn"
171
+ 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 hover:shadow-primary/50 hover:scale-105 active:scale-95">
172
+ <span class="material-symbols-outlined text-lg">analytics</span>
173
+ Analyze Scenes
174
+ </button>
175
+ </div>
176
 
177
+ <!-- Review Panel -->
178
+ <div id="review-panel"
179
+ class="hidden glass-panel rounded-3xl p-5 shadow-2xl flex flex-col gap-4 animate-in slide-in-from-bottom-8 fade-in-0 duration-300 border-primary/20 ring-1 ring-white/10">
180
+ <div class="flex items-center justify-between border-b border-white/10 pb-3">
181
+ <h3 class="text-sm font-bold text-white flex items-center gap-2">
182
+ <span class="material-symbols-outlined text-primary">movie_edit</span>
183
+ Director's Configuration
184
+ </h3>
185
+ <button onclick="resetUI()"
186
+ class="text-xs text-gray-500 hover:text-white uppercase tracking-wider flex items-center gap-1 hover:bg-white/10 px-2 py-1 rounded transition-colors">
187
+ <span class="material-symbols-outlined text-[14px]">restart_alt</span> Reset
188
+ </button>
189
+ </div>
 
 
 
 
 
 
 
 
190
 
191
+ <div>
192
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
193
+ Direction</label>
194
+ <textarea id="prompt-box" rows="2"
195
+ class="w-full bg-black/20 border border-white/10 rounded-xl p-3 text-sm text-white focus:border-primary focus:ring-1 focus:ring-primary outline-none resize-none transition-all placeholder:text-gray-600"></textarea>
196
+ </div>
197
 
198
+ <div class="grid grid-cols-2 gap-4">
199
+ <div>
200
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
201
+ Style</label>
202
+ <select id="style-select"
203
+ class="w-full bg-black/20 border border-white/10 rounded-xl p-2.5 text-sm text-white focus:border-primary outline-none transition-all cursor-pointer">
204
+ <option value="Cinematic">Cinematic</option>
205
+ <option value="Anime">Anime</option>
206
+ <option value="Cyberpunk">Cyberpunk</option>
207
+ <option value="VHS">VHS Glitch</option>
208
+ <option value="Noir">Noir</option>
209
+ <option value="Claymation">Claymation</option>
210
+ </select>
211
+ </div>
212
+ <div>
213
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio
214
+ Mood</label>
215
+ <input id="audio-input" type="text"
216
+ class="w-full bg-black/20 border border-white/10 rounded-xl p-2.5 text-sm text-white focus:border-primary outline-none transition-all"
217
+ value="Cinematic ambient sound" placeholder="e.g. Dark industrial...">
218
+ </div>
219
  </div>
220
+
221
+ <button id="generate-btn"
222
+ 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-2 transition-all hover:scale-[1.02] active:scale-[0.98]">
223
+ <span class="material-symbols-outlined">auto_fix_high</span>
224
+ Generate Video
225
+ </button>
226
  </div>
 
227
 
228
+ </div>
229
+
230
+ <!-- Logic -->
231
  <script>
232
  let currentVideoAPath = "";
233
  let currentVideoCPath = "";
234
 
 
235
  function handleFileSelect(input, labelId) {
236
  if (input.files && input.files[0]) {
237
  const label = document.getElementById(labelId);
238
  label.innerText = input.files[0].name;
239
+ label.classList.add("text-primary", "font-bold");
240
  }
241
  }
242
+
243
  function resetUI() {
244
+ document.getElementById("analysis-panel").classList.remove("hidden");
245
+ document.getElementById("review-panel").classList.add("hidden");
246
+ document.getElementById("prompt-box").value = "";
247
+ currentVideoAPath = "";
248
+ currentVideoCPath = "";
249
  }
250
 
251
+ // --- ANALYZE LOGIC ---
252
+ document.getElementById("analyze-btn").addEventListener("click", async () => {
253
+ const fileA = document.getElementById("video-upload-a").files[0];
254
+ const fileC = document.getElementById("video-upload-c").files[0];
255
+ const btn = document.getElementById("analyze-btn");
 
 
 
 
 
 
 
 
256
 
257
+ if (!fileA || !fileC) {
258
+ alert("⚠️ Please upload both Scene A and Scene C first.");
259
+ return;
260
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
+ const originalText = btn.innerHTML;
263
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Analyzing...`;
264
+ btn.disabled = true;
265
+
266
+ const formData = new FormData();
267
+ formData.append("video_a", fileA);
268
+ formData.append("video_c", fileC);
269
+
270
+ try {
271
+ const res = await fetch("/analyze", { method: "POST", body: formData });
272
+ if (!res.ok) throw new Error(await res.text());
273
+
274
+ const data = await res.json();
 
 
275
 
276
+ // Switch UI
277
+ document.getElementById("prompt-box").value = data.prompt;
278
+ currentVideoAPath = data.video_a_path;
279
+ currentVideoCPath = data.video_c_path;
280
+
281
+ document.getElementById("analysis-panel").classList.add("hidden");
282
+ document.getElementById("review-panel").classList.remove("hidden");
283
+ } catch (err) {
284
+ alert("Analysis Error: " + err.message);
285
+ } finally {
286
+ btn.innerHTML = originalText;
287
+ btn.disabled = false;
288
+ }
289
+ });
290
+
291
+ // --- GENERATE LOGIC ---
292
+ document.getElementById("generate-btn").addEventListener("click", async () => {
293
+ const btn = document.getElementById("generate-btn");
294
+ const prompt = document.getElementById("prompt-box").value;
295
+ const style = document.getElementById("style-select").value;
296
+ const audio = document.getElementById("audio-input").value;
297
+
298
+ const originalText = btn.innerHTML;
299
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Generating (this takes ~60s)...`;
300
+ btn.disabled = true;
301
+ btn.classList.add("opacity-50");
302
+
303
+ try {
304
+ const res = await fetch("/generate", {
305
+ method: "POST",
306
+ headers: { "Content-Type": "application/json" },
307
+ body: JSON.stringify({
308
+ prompt: prompt,
309
+ style: style,
310
+ audio_prompt: audio,
311
+ video_a_path: currentVideoAPath,
312
+ video_c_path: currentVideoCPath
313
+ })
314
+ });
315
+
316
+ if (!res.ok) throw new Error(await res.text());
317
+
318
+ const data = await res.json();
319
+ const jobId = data.job_id;
320
+
321
+ // Poll Status
322
+ const poll = setInterval(async () => {
323
+ try {
324
+ const statusRes = await fetch(`/status/${jobId}?t=${Date.now()}`);
325
+ if (statusRes.ok) {
326
+ const status = await statusRes.json();
327
+
328
+ if (status.status === "completed") {
329
+ clearInterval(poll);
330
  const bridgeCard = document.getElementById("bridge-card");
331
+ bridgeCard.innerHTML = `
332
+ <video controls autoplay loop class="w-full h-full object-cover">
333
+ <source src="${status.video_url}" type="video/mp4">
334
+ </video>
335
+ `;
336
+ // Reset Button
337
+ btn.innerHTML = `<span class="material-symbols-outlined">check_circle</span> Done!`;
338
+ setTimeout(() => {
339
+ btn.innerHTML = originalText;
340
+ btn.disabled = false;
341
+ btn.classList.remove("opacity-50");
342
+ }, 3000);
343
+ } else if (status.status === "error") {
344
+ clearInterval(poll);
345
+ alert("Generation Error: " + status.log);
346
+ btn.innerHTML = originalText;
 
 
 
 
 
347
  btn.disabled = false;
348
+ btn.classList.remove("opacity-50");
349
+ } else {
350
+ // Update log text
351
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> ${status.log} (${status.progress}%)`;
352
  }
 
 
353
  }
354
+ } catch (e) {
355
+ console.error(e);
356
+ }
357
+ }, 1500);
358
+
359
+ } catch (err) {
360
+ alert("Request Error: " + err.message);
361
+ btn.innerHTML = originalText;
362
+ btn.disabled = false;
363
+ btn.classList.remove("opacity-50");
364
+ }
365
+ });
366
  </script>
367
  </body>
368
+
369
  </html>