Gaurav vashistha commited on
Commit
d69d44c
·
1 Parent(s): 72ade06

fix(frontend): restored missing html tags and structure in code.html

Browse files
Files changed (1) hide show
  1. stitch_continuity_dashboard/code.html +480 -346
stitch_continuity_dashboard/code.html CHANGED
@@ -1,370 +1,504 @@
1
  <!DOCTYPE html>
2
  <html class="dark" lang="en">
3
- <div class="flex items-center gap-3">
4
- <div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary">
5
- <span class="material-symbols-outlined">movie_filter</span>
6
- </div>
7
- <h1 class="text-xl font-display font-bold tracking-tight">Continuity <span
8
- class="opacity-50 font-normal text-sm ml-2">v2.3</span></h1>
9
- </div>
10
- <div class="flex items-center gap-3">
11
- <button id="history-btn"
12
- class="flex items-center gap-2 px-4 py-2 bg-surface-dark border border-white/10 rounded-lg hover:bg-white/5 transition-colors text-xs font-bold uppercase tracking-wider">
13
- <span class="material-symbols-outlined text-lg">history</span>
14
- Gallery
15
- </button>
16
- <div class="flex items-center gap-2 px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
17
- <div class="size-2 bg-green-500 rounded-full animate-pulse"></div>
18
- <span class="text-xs font-bold text-green-400 tracking-wide uppercase">System Online</span>
19
- </div>
20
- </div>
21
- <div class="w-full max-w-6xl flex items-center justify-center gap-4 md:gap-8 lg:gap-12 mt-20">
22
 
23
- <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
24
- <div class="flex justify-between px-1">
25
- <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span>
26
- </div>
27
- <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"
28
- onclick="document.getElementById('video-upload-a').click()">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  <div
30
- class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
31
- <span class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
32
  </div>
33
- <p id="label-a" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload Start Clip
34
- </p>
35
- <input type="file" id="video-upload-a" accept="video/*" class="hidden"
36
- onchange="handleFileSelect(this, 'label-a')">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  </div>
38
  </div>
39
- <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20">
40
- <div class="flex justify-center px-1">
41
- <span class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated
42
- Bridge</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  </div>
44
 
45
- <div id="bridge-card-outer"
46
- class="relative aspect-video rounded-2xl shadow-neon transition-all duration-500 border border-primary/20">
47
- <div id="bridge-card-inner" class="force-clip w-full h-full bg-black relative">
48
- <div id="bridge-content" class="w-full h-full">
49
- <div
50
- 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">
51
- </div>
52
- <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
 
 
 
53
  <div
54
- class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
55
- <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
 
 
 
 
 
 
56
  </div>
57
- <p class="text-sm text-gray-300">Ready to bridge the gap</p>
58
  </div>
 
 
59
  </div>
60
- <div id="bridge-border"
61
- class="absolute inset-0 rounded-2xl border-2 border-transparent pointer-events-none z-30"></div>
62
  </div>
63
  </div>
64
- </div>
65
- <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
66
- <div class="flex justify-end px-1">
67
- <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span>
68
- </div>
69
- <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"
70
- onclick="document.getElementById('video-upload-c').click()">
71
- <div
72
- class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
73
- <span class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
 
 
 
 
 
 
74
  </div>
75
- <p id="label-c" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload End Clip</p>
76
- <input type="file" id="video-upload-c" accept="video/*" class="hidden"
77
- onchange="handleFileSelect(this, 'label-c')">
78
  </div>
79
  </div>
80
- </div>
81
- <div class="p-6 border-b border-white/5 flex items-center justify-between">
82
- <h2 class="text-lg font-bold text-white flex items-center gap-2">
83
- <span class="material-symbols-outlined text-primary">history</span>
84
- Creation History
85
- </h2>
86
- <button id="close-drawer" class="text-gray-400 hover:text-white transition-colors">
87
- <span class="material-symbols-outlined">close</span>
88
- </button>
89
- </div>
90
- <div id="gallery-content" class="flex-1 overflow-y-auto p-4 space-y-4">
91
- <div class="text-center text-gray-500 mt-10">Loading history...</div>
92
- </div>
93
- <div id="analysis-panel" class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2">
94
- <div class="flex flex-col">
95
- <span class="text-sm font-bold text-white">Continuity Engine</span>
96
- <span class="text-[10px] text-gray-400 uppercase tracking-wide">Ready for analysis</span>
97
- </div>
98
- <button id="analyze-btn"
99
- 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">
100
- <span class="material-symbols-outlined text-lg">analytics</span>
101
- Analyze Scenes
102
- </button>
103
- </div>
104
- <div id="review-panel"
105
- class="hidden glass-panel rounded-2xl p-5 shadow-2xl flex flex-col gap-4 animate-in slide-in-from-bottom-4 duration-300">
106
- <div class="flex items-center justify-between border-b border-white/10 pb-3">
107
- <h3 class="text-sm font-bold text-white flex items-center gap-2">
108
- <span class="material-symbols-outlined text-primary">movie_edit</span>
109
- Director's Configuration
110
- </h3>
111
- <button onclick="resetUI()"
112
- class="text-xs text-gray-500 hover:text-white uppercase tracking-wider">Reset</button>
113
- </div>
114
- <div>
115
- <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual Direction</label>
116
- <textarea id="prompt-box" rows="2"
117
- 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>
118
- </div>
119
- <div class="grid grid-cols-2 gap-4">
120
- <div>
121
- <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual Style</label>
122
- <select id="style-select" onchange="savePreference('style', this.value)"
123
- class="w-full bg-black/20 border border-white/10 rounded-lg p-2.5 text-sm text-white focus:border-primary outline-none">
124
- <option value="Cinematic">Cinematic</option>
125
- <option value="Anime">Anime</option>
126
- <option value="Cyberpunk">Cyberpunk</option>
127
- <option value="VHS">VHS Glitch</option>
128
- <option value="Noir">Noir</option>
129
- </select>
130
- </div>
131
- <div>
132
- <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio Mood</label>
133
- <select id="audio-input" onchange="savePreference('audio', this.value)"
134
- 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">
135
- <option value="Cinematic orchestral score, epic, dramatic, high fidelity">Cinematic (Default)</option>
136
- <option value="Industrial synthwave, futuristic, neon hum, electronic">Cyberpunk / Sci-Fi</option>
137
- <option value="Nature sounds, wind, birds, flowing water, organic">Nature / Organic</option>
138
- <option value="Tense atmosphere, suspenseful drone, horror, scary">Horror / Suspense</option>
139
- <option value="Lo-fi hip hop, chill, relaxing, soft beats">Lo-fi / Chill</option>
140
- <option value="High energy, rock, intense, fast paced">Action / Intense</option>
141
- </select>
142
  </div>
143
- </div>
144
- <button id="generate-btn"
145
- 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">
146
- <span class="material-symbols-outlined">auto_fix_high</span>
147
- Generate Video
148
- </button>
149
- </div>
150
- // --- SESSION PERSISTENCE ---
151
- function savePreference(key, value) {
152
- localStorage.setItem('continuity_' + key, value);
153
- }
154
- function loadPreferences() {
155
- const savedStyle = localStorage.getItem('continuity_style');
156
- const savedAudio = localStorage.getItem('continuity_audio');
157
-
158
- if (savedStyle) document.getElementById('style-select').value = savedStyle;
159
- if (savedAudio) document.getElementById('audio-input').value = savedAudio;
160
- }
161
- // Load prefs on init
162
- loadPreferences();
163
- // --- HISTORY GALLERY LOGIC ---
164
- const drawer = document.getElementById('gallery-drawer');
165
- const overlay = document.getElementById('drawer-overlay');
166
- const historyBtn = document.getElementById('history-btn');
167
- const closeBtn = document.getElementById('close-drawer');
168
- function toggleDrawer(show) {
169
- if (show) {
170
- drawer.classList.remove('drawer-closed');
171
- drawer.classList.add('drawer-open');
172
- overlay.classList.remove('hidden');
173
- fetchHistory();
174
- } else {
175
- drawer.classList.remove('drawer-open');
176
- drawer.classList.add('drawer-closed');
177
- overlay.classList.add('hidden');
178
- }
179
- }
180
- historyBtn.addEventListener('click', () => toggleDrawer(true));
181
- closeBtn.addEventListener('click', () => toggleDrawer(false));
182
- overlay.addEventListener('click', () => toggleDrawer(false));
183
- async function fetchHistory() {
184
- const container = document.getElementById('gallery-content');
185
- container.innerHTML = '<div class="text-center text-gray-500 mt-10"><span
186
- class="material-symbols-outlined animate-spin text-2xl">progress_activity</span></div>';
187
-
188
- try {
189
- const res = await fetch('/history');
190
- const data = await res.json();
191
-
192
- if (!data || data.length === 0) {
193
- container.innerHTML = '<div class="text-center text-gray-500 mt-10 text-sm">No history found. Start creating!</div>';
194
- return;
195
- }
196
- container.innerHTML = data.map(item => `
197
- <div
198
- class="bg-black/40 rounded-xl overflow-hidden border border-white/5 hover:border-primary/50 transition-colors group">
199
- <div class="aspect-video relative bg-black">
200
- <video src="${item.url}" class="w-full h-full object-cover" controls preload="metadata"></video>
201
- </div>
202
- <div class="p-3 flex items-center justify-between">
203
- <div>
204
- <p class="text-xs text-gray-400 font-mono truncate w-40">${item.name}</p>
205
- <p class="text-[10px] text-gray-600">${new Date(item.created).toLocaleDateString()}</p>
206
  </div>
207
- <a href="${item.url}" download target="_blank"
208
- class="p-2 bg-white/5 hover:bg-primary hover:text-white rounded-lg transition-colors" title="Download">
209
- <span class="material-symbols-outlined text-sm">download</span>
210
- </a>
211
- </div>
212
- </div>
213
- `).join('');
214
-
215
- } catch (e) {
216
- container.innerHTML = '<div class="text-center text-red-400 mt-10 text-sm">Failed to load history.</div>';
217
- console.error(e);
218
- }
219
- }
220
- // --- CORE LOGIC ---
221
- function handleFileSelect(input, labelId) {
222
- if (input.files && input.files[0]) {
223
- const label = document.getElementById(labelId);
224
- label.innerText = input.files[0].name;
225
- label.classList.add("text-primary", "font-bold");
226
- }
227
- }
228
- function resetUI() {
229
- document.getElementById("analysis-panel").classList.remove("hidden");
230
- document.getElementById("review-panel").classList.add("hidden");
231
- document.getElementById("prompt-box").value = "";
232
-
233
- // Reset Bridge
234
- document.getElementById("bridge-content").innerHTML = `
235
- <div
236
- 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">
237
- </div>
238
- <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
239
- <div class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
240
- <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
241
  </div>
242
- <p class="text-sm text-gray-300">Ready to bridge the gap</p>
243
- </div>
244
- `;
245
- // Reset Outer Glow
246
- const outer = document.getElementById("bridge-card-outer");
247
- outer.classList.remove("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
248
- outer.classList.add("border-primary/20");
249
-
250
- // Reset Inner Border
251
- const border = document.getElementById("bridge-border");
252
- border.classList.remove("border-primary/50");
253
- border.classList.add("border-transparent");
254
- currentVideoAPath = "";
255
- currentVideoCPath = "";
256
- }
257
- // --- ANALYZE LOGIC ---
258
- document.getElementById("analyze-btn").addEventListener("click", async () => {
259
- const fileA = document.getElementById("video-upload-a").files[0];
260
- const fileC = document.getElementById("video-upload-c").files[0];
261
- const btn = document.getElementById("analyze-btn");
262
- if (!fileA || !fileC) {
263
- alert("⚠️ Please upload both Scene A and Scene C first.");
264
- return;
265
- }
266
- const originalText = btn.innerHTML;
267
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Analyzing...`;
268
- btn.disabled = true;
269
- const formData = new FormData();
270
- formData.append("video_a", fileA);
271
- formData.append("video_c", fileC);
272
- try {
273
- const res = await fetch("/analyze", { method: "POST", body: formData });
274
- if (!res.ok) throw new Error(await res.text());
275
-
276
- const data = await res.json();
277
-
278
- document.getElementById("prompt-box").value = data.prompt;
279
- currentVideoAPath = data.video_a_path;
280
- currentVideoCPath = data.video_c_path;
281
-
282
- document.getElementById("analysis-panel").classList.add("hidden");
283
- document.getElementById("review-panel").classList.remove("hidden");
284
- } catch (err) {
285
- alert("Analysis Error: " + err.message);
286
- } finally {
287
- btn.innerHTML = originalText;
288
- btn.disabled = false;
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
- const originalText = btn.innerHTML;
298
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Generating (this
299
- takes ~60s)...`;
300
- btn.disabled = true;
301
- btn.classList.add("opacity-50");
302
- try {
303
- const res = await fetch("/generate", {
304
- method: "POST",
305
- headers: { "Content-Type": "application/json" },
306
- body: JSON.stringify({
307
- prompt: prompt,
308
- style: style,
309
- audio_prompt: audio,
310
- video_a_path: currentVideoAPath,
311
- video_c_path: currentVideoCPath
312
- })
313
- });
314
- if (!res.ok) throw new Error(await res.text());
315
- const data = await res.json();
316
- const jobId = data.job_id;
317
- // Poll Status
318
- const poll = setInterval(async () => {
319
- const statusRes = await fetch(`/status/${jobId}?t=${Date.now()}`);
320
- if (statusRes.ok) {
321
- const status = await statusRes.json();
322
-
323
- if (status.status === "completed") {
324
- clearInterval(poll);
325
-
326
- // Render Video (Object-Contain prevents zoom overlap)
327
- const bridgeContent = document.getElementById("bridge-content");
328
- bridgeContent.innerHTML = `
329
- <video controls autoplay loop class="w-full h-full object-contain bg-black">
330
- <source src="${status.video_url}" type="video/mp4">
331
- </video>
332
- `;
333
-
334
- // Activate Outer Glow
335
- const outer = document.getElementById("bridge-card-outer");
336
- outer.classList.remove("border-primary/20");
337
- outer.classList.add("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
338
- // Activate Inner Overlay Border
339
- const border = document.getElementById("bridge-border");
340
- border.classList.remove("border-transparent");
341
- border.classList.add("border-primary/50");
342
- btn.innerHTML = `<span class="material-symbols-outlined">check_circle</span> Done!`;
343
- setTimeout(() => {
344
- btn.innerHTML = originalText;
345
- btn.disabled = false;
346
- btn.classList.remove("opacity-50");
347
- }, 3000);
348
- } else if (status.status === "error") {
349
- clearInterval(poll);
350
- alert("Generation Error: " + status.log);
351
- btn.innerHTML = originalText;
352
- btn.disabled = false;
353
- btn.classList.remove("opacity-50");
354
- } else {
355
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> ${status.log}
356
- (${status.progress}%)`;
357
- }
358
- }
359
- }, 1500);
360
- } catch (err) {
361
- alert("Request Error: " + err.message);
362
- btn.innerHTML = originalText;
363
- btn.disabled = false;
364
- btn.classList.remove("opacity-50");
365
- }
366
- });
367
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  </body>
369
 
370
  </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 v2.3</title>
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script>
11
+ tailwind.config = {
12
+ darkMode: 'class',
13
+ theme: {
14
+ extend: {
15
+ colors: {
16
+ primary: '#7f0df2',
17
+ 'surface-dark': '#0a0a0a',
18
+ 'border-dark': '#2a2a2a'
19
+ },
20
+ fontFamily: {
21
+ display: ['Inter', 'sans-serif'],
22
+ },
23
+ boxShadow: {
24
+ 'neon': '0 0 20px rgba(127, 13, 242, 0.3)',
25
+ }
26
+ }
27
+ }
28
+ }
29
+ </script>
30
+ <!-- Google Fonts & Icons -->
31
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
32
+ <link rel="stylesheet"
33
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0" />
34
+ <style>
35
+ body {
36
+ font-family: 'Inter', sans-serif;
37
+ background-color: #000;
38
+ color: #fff;
39
+ }
40
+
41
+ .glass-panel {
42
+ background: rgba(255, 255, 255, 0.03);
43
+ backdrop-filter: blur(10px);
44
+ border: 1px solid rgba(255, 255, 255, 0.05);
45
+ }
46
+
47
+ .drawer-open {
48
+ transform: translateX(0);
49
+ }
50
+
51
+ .drawer-closed {
52
+ transform: translateX(100%);
53
+ }
54
+
55
+ /* Custom Scrollbar */
56
+ ::-webkit-scrollbar {
57
+ width: 6px;
58
+ }
59
+
60
+ ::-webkit-scrollbar-track {
61
+ background: #000;
62
+ }
63
+
64
+ ::-webkit-scrollbar-thumb {
65
+ background: #333;
66
+ border-radius: 10px;
67
+ }
68
+
69
+ ::-webkit-scrollbar-thumb:hover {
70
+ background: #7f0df2;
71
+ }
72
+ </style>
73
+ </head>
74
+
75
+ <body class="bg-black min-h-screen flex flex-col items-center relative overflow-x-hidden selection:bg-primary/30">
76
+
77
+ <!-- HEADER -->
78
+ <header
79
+ class="w-full fixed top-0 z-50 bg-black/50 backdrop-blur-md border-b border-white/5 h-16 flex items-center justify-between px-6 lg:px-12">
80
+ <div class="flex items-center gap-3">
81
  <div
82
+ class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary">
83
+ <span class="material-symbols-outlined">movie_filter</span>
84
  </div>
85
+ <h1 class="text-xl font-display font-bold tracking-tight">Continuity <span
86
+ class="opacity-50 font-normal text-sm ml-2">v2.3</span></h1>
87
+ </div>
88
+ <div class="flex items-center gap-3">
89
+ <button id="history-btn"
90
+ class="flex items-center gap-2 px-4 py-2 bg-surface-dark border border-white/10 rounded-lg hover:bg-white/5 transition-colors text-xs font-bold uppercase tracking-wider">
91
+ <span class="material-symbols-outlined text-lg">history</span>
92
+ Gallery
93
+ </button>
94
+ <div class="flex items-center gap-2 px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
95
+ <div class="size-2 bg-green-500 rounded-full animate-pulse"></div>
96
+ <span class="text-xs font-bold text-green-400 tracking-wide uppercase">System Online</span>
97
+ </div>
98
+ </div>
99
+ </header>
100
+
101
+ <!-- GALLERY DRAWER -->
102
+ <div id="drawer-overlay"
103
+ class="fixed inset-0 bg-black/80 backdrop-blur-sm z-[60] hidden transition-opacity duration-300"></div>
104
+ <div id="gallery-drawer"
105
+ class="fixed top-0 right-0 h-full w-full max-w-md bg-[#0a0a0a] border-l border-white/10 z-[70] shadow-2xl drawer-closed transition-transform duration-300 ease-in-out flex flex-col">
106
+ <div class="p-6 border-b border-white/5 flex items-center justify-between">
107
+ <h2 class="text-lg font-bold text-white flex items-center gap-2">
108
+ <span class="material-symbols-outlined text-primary">history</span>
109
+ Creation History
110
+ </h2>
111
+ <button id="close-drawer" class="text-gray-400 hover:text-white transition-colors">
112
+ <span class="material-symbols-outlined">close</span>
113
+ </button>
114
+ </div>
115
+ <div id="gallery-content" class="flex-1 overflow-y-auto p-4 space-y-4">
116
+ <div class="text-center text-gray-500 mt-10">Loading history...</div>
117
  </div>
118
  </div>
119
+
120
+ <!-- MAIN CONTENT -->
121
+ <div class="w-full max-w-6xl flex items-center justify-center gap-4 md:gap-8 lg:gap-12 mt-20">
122
+ <!-- Scene A -->
123
+ <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
124
+ <div class="flex justify-between px-1">
125
+ <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span>
126
+ </div>
127
+ <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"
128
+ onclick="document.getElementById('video-upload-a').click()">
129
+ <div
130
+ class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
131
+ <span class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
132
+ </div>
133
+ <p id="label-a" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload Start
134
+ Clip</p>
135
+ <input type="file" id="video-upload-a" accept="video/*" class="hidden"
136
+ onchange="handleFileSelect(this, 'label-a')">
137
+ </div>
138
  </div>
139
 
140
+ <!-- Bridge -->
141
+ <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20">
142
+ <div class="flex justify-center px-1">
143
+ <span class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated
144
+ Bridge</span>
145
+ </div>
146
+
147
+ <div id="bridge-card-outer"
148
+ class="relative aspect-video rounded-2xl shadow-neon transition-all duration-500 border border-primary/20">
149
+ <div id="bridge-card-inner" class="force-clip w-full h-full bg-black relative">
150
+ <div id="bridge-content" class="w-full h-full">
151
  <div
152
+ 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">
153
+ </div>
154
+ <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
155
+ <div
156
+ class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
157
+ <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
158
+ </div>
159
+ <p class="text-sm text-gray-300">Ready to bridge the gap</p>
160
  </div>
 
161
  </div>
162
+ <div id="bridge-border"
163
+ class="absolute inset-0 rounded-2xl border-2 border-transparent pointer-events-none z-30"></div>
164
  </div>
 
 
165
  </div>
166
  </div>
167
+
168
+ <!-- Scene C -->
169
+ <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
170
+ <div class="flex justify-end px-1">
171
+ <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span>
172
+ </div>
173
+ <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"
174
+ onclick="document.getElementById('video-upload-c').click()">
175
+ <div
176
+ class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
177
+ <span class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
178
+ </div>
179
+ <p id="label-c" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload End Clip
180
+ </p>
181
+ <input type="file" id="video-upload-c" accept="video/*" class="hidden"
182
+ onchange="handleFileSelect(this, 'label-c')">
183
  </div>
 
 
 
184
  </div>
185
  </div>
186
+
187
+ <!-- CONTROLS -->
188
+ <div class="fixed bottom-10 z-40 flex flex-col gap-4 w-full max-w-xl px-6">
189
+
190
+ <!-- Analysis Panel -->
191
+ <div id="analysis-panel"
192
+ class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2">
193
+ <div class="flex flex-col">
194
+ <span class="text-sm font-bold text-white">Continuity Engine</span>
195
+ <span class="text-[10px] text-gray-400 uppercase tracking-wide">Ready for analysis</span>
196
+ </div>
197
+ <button id="analyze-btn"
198
+ 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">
199
+ <span class="material-symbols-outlined text-lg">analytics</span>
200
+ Analyze Scenes
201
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  </div>
203
+
204
+ <!-- Review Panel -->
205
+ <div id="review-panel"
206
+ class="hidden glass-panel rounded-2xl p-5 shadow-2xl flex flex-col gap-4 animate-in slide-in-from-bottom-4 duration-300">
207
+ <div class="flex items-center justify-between border-b border-white/10 pb-3">
208
+ <h3 class="text-sm font-bold text-white flex items-center gap-2">
209
+ <span class="material-symbols-outlined text-primary">movie_edit</span>
210
+ Director's Configuration
211
+ </h3>
212
+ <button onclick="resetUI()"
213
+ class="text-xs text-gray-500 hover:text-white uppercase tracking-wider">Reset</button>
214
+ </div>
215
+
216
+ <div>
217
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
218
+ Direction</label>
219
+ <textarea id="prompt-box" rows="2"
220
+ 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>
221
+ </div>
222
+
223
+ <div class="grid grid-cols-2 gap-4">
224
+ <div>
225
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
226
+ Style</label>
227
+ <select id="style-select" onchange="savePreference('style', this.value)"
228
+ 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">
229
+ <option value="Cinematic">Cinematic</option>
230
+ <option value="Anime">Anime</option>
231
+ <option value="Cyberpunk">Cyberpunk</option>
232
+ <option value="VHS">VHS Glitch</option>
233
+ <option value="Noir">Noir</option>
234
+ </select>
235
+ </div>
236
+ <div>
237
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio
238
+ Mood</label>
239
+ <select id="audio-input" onchange="savePreference('audio', this.value)"
240
+ 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">
241
+ <option value="Cinematic orchestral score, epic, dramatic, high fidelity">Cinematic (Default)
242
+ </option>
243
+ <option value="Industrial synthwave, futuristic, neon hum, electronic">Cyberpunk / Sci-Fi
244
+ </option>
245
+ <option value="Nature sounds, wind, birds, flowing water, organic">Nature / Organic</option>
246
+ <option value="Tense atmosphere, suspenseful drone, horror, scary">Horror / Suspense</option>
247
+ <option value="Lo-fi hip hop, chill, relaxing, soft beats">Lo-fi / Chill</option>
248
+ <option value="High energy, rock, intense, fast paced">Action / Intense</option>
249
+ </select>
250
+ </div>
251
+ </div>
252
+
253
+ <button id="generate-btn"
254
+ 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">
255
+ <span class="material-symbols-outlined">auto_fix_high</span>
256
+ Generate Video
257
+ </button>
 
 
 
 
 
 
 
 
258
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  </div>
260
+
261
+ <script>
262
+ // --- SESSION PERSISTENCE ---
263
+ function savePreference(key, value) {
264
+ localStorage.setItem('continuity_' + key, value);
265
+ }
266
+
267
+ function loadPreferences() {
268
+ const savedStyle = localStorage.getItem('continuity_style');
269
+ const savedAudio = localStorage.getItem('continuity_audio');
270
+
271
+ if (savedStyle) document.getElementById('style-select').value = savedStyle;
272
+ if (savedAudio) document.getElementById('audio-input').value = savedAudio;
273
+ }
274
+
275
+ // Load prefs on init
276
+ loadPreferences();
277
+
278
+ // --- HISTORY GALLERY LOGIC ---
279
+ const drawer = document.getElementById('gallery-drawer');
280
+ const overlay = document.getElementById('drawer-overlay');
281
+ const historyBtn = document.getElementById('history-btn');
282
+ const closeBtn = document.getElementById('close-drawer');
283
+
284
+ function toggleDrawer(show) {
285
+ if (show) {
286
+ drawer.classList.remove('drawer-closed');
287
+ drawer.classList.add('drawer-open');
288
+ overlay.classList.remove('hidden');
289
+ fetchHistory();
290
+ } else {
291
+ drawer.classList.remove('drawer-open');
292
+ drawer.classList.add('drawer-closed');
293
+ overlay.classList.add('hidden');
294
+ }
295
+ }
296
+
297
+ historyBtn.addEventListener('click', () => toggleDrawer(true));
298
+ closeBtn.addEventListener('click', () => toggleDrawer(false));
299
+ overlay.addEventListener('click', () => toggleDrawer(false));
300
+
301
+ async function fetchHistory() {
302
+ const container = document.getElementById('gallery-content');
303
+ container.innerHTML = '<div class="text-center text-gray-500 mt-10"><span class="material-symbols-outlined animate-spin text-2xl">progress_activity</span></div>';
304
+
305
+ try {
306
+ const res = await fetch('/history');
307
+ const data = await res.json();
308
+
309
+ if (!data || data.length === 0) {
310
+ container.innerHTML = '<div class="text-center text-gray-500 mt-10 text-sm">No history found. Start creating!</div>';
311
+ return;
312
+ }
313
+
314
+ container.innerHTML = data.map(item => `
315
+ <div class="bg-black/40 rounded-xl overflow-hidden border border-white/5 hover:border-primary/50 transition-colors group">
316
+ <div class="aspect-video relative bg-black">
317
+ <video src="${item.url}" class="w-full h-full object-cover" controls preload="metadata"></video>
318
+ </div>
319
+ <div class="p-3 flex items-center justify-between">
320
+ <div>
321
+ <p class="text-xs text-gray-400 font-mono truncate w-40">${item.name}</p>
322
+ <p class="text-[10px] text-gray-600">${new Date(item.created).toLocaleDateString()}</p>
323
+ </div>
324
+ <a href="${item.url}" download target="_blank" class="p-2 bg-white/5 hover:bg-primary hover:text-white rounded-lg transition-colors" title="Download">
325
+ <span class="material-symbols-outlined text-sm">download</span>
326
+ </a>
327
+ </div>
328
+ </div>
329
+ `).join('');
330
+
331
+ } catch (e) {
332
+ container.innerHTML = '<div class="text-center text-red-400 mt-10 text-sm">Failed to load history.</div>';
333
+ console.error(e);
334
+ }
335
+ }
336
+
337
+ // --- CORE LOGIC ---
338
+ let currentVideoAPath = "";
339
+ let currentVideoCPath = "";
340
+
341
+ function handleFileSelect(input, labelId) {
342
+ if (input.files && input.files[0]) {
343
+ const label = document.getElementById(labelId);
344
+ label.innerText = input.files[0].name;
345
+ label.classList.add("text-primary", "font-bold");
346
+ }
347
+ }
348
+
349
+ function resetUI() {
350
+ document.getElementById("analysis-panel").classList.remove("hidden");
351
+ document.getElementById("review-panel").classList.add("hidden");
352
+ document.getElementById("prompt-box").value = "";
353
+
354
+ // Reset Bridge
355
+ document.getElementById("bridge-content").innerHTML = `
356
+ <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>
357
+ <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
358
+ <div class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
359
+ <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
360
+ </div>
361
+ <p class="text-sm text-gray-300">Ready to bridge the gap</p>
362
+ </div>
363
+ `;
364
+ // Reset Outer Glow
365
+ const outer = document.getElementById("bridge-card-outer");
366
+ outer.classList.remove("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
367
+ outer.classList.add("border-primary/20");
368
+
369
+ // Reset Inner Border
370
+ const border = document.getElementById("bridge-border");
371
+ border.classList.remove("border-primary/50");
372
+ border.classList.add("border-transparent");
373
+
374
+ currentVideoAPath = "";
375
+ currentVideoCPath = "";
376
+ }
377
+
378
+ // --- ANALYZE LOGIC ---
379
+ document.getElementById("analyze-btn").addEventListener("click", async () => {
380
+ const fileA = document.getElementById("video-upload-a").files[0];
381
+ const fileC = document.getElementById("video-upload-c").files[0];
382
+ const btn = document.getElementById("analyze-btn");
383
+
384
+ if (!fileA || !fileC) {
385
+ alert("⚠️ Please upload both Scene A and Scene C first.");
386
+ return;
387
+ }
388
+
389
+ const originalText = btn.innerHTML;
390
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Analyzing...`;
391
+ btn.disabled = true;
392
+
393
+ const formData = new FormData();
394
+ formData.append("video_a", fileA);
395
+ formData.append("video_c", fileC);
396
+
397
+ try {
398
+ const res = await fetch("/analyze", { method: "POST", body: formData });
399
+ if (!res.ok) throw new Error(await res.text());
400
+
401
+ const data = await res.json();
402
+
403
+ document.getElementById("prompt-box").value = data.prompt;
404
+ currentVideoAPath = data.video_a_path;
405
+ currentVideoCPath = data.video_c_path;
406
+
407
+ document.getElementById("analysis-panel").classList.add("hidden");
408
+ document.getElementById("review-panel").classList.remove("hidden");
409
+
410
+ } catch (err) {
411
+ alert("Analysis Error: " + err.message);
412
+ } finally {
413
+ btn.innerHTML = originalText;
414
+ btn.disabled = false;
415
+ }
416
+ });
417
+
418
+ // --- GENERATE LOGIC ---
419
+ document.getElementById("generate-btn").addEventListener("click", async () => {
420
+ const btn = document.getElementById("generate-btn");
421
+ const prompt = document.getElementById("prompt-box").value;
422
+ const style = document.getElementById("style-select").value;
423
+ const audio = document.getElementById("audio-input").value;
424
+
425
+ const originalText = btn.innerHTML;
426
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Generating (this takes ~60s)...`;
427
+ btn.disabled = true;
428
+ btn.classList.add("opacity-50");
429
+
430
+ try {
431
+ const res = await fetch("/generate", {
432
+ method: "POST",
433
+ headers: { "Content-Type": "application/json" },
434
+ body: JSON.stringify({
435
+ prompt: prompt,
436
+ style: style,
437
+ audio_prompt: audio,
438
+ video_a_path: currentVideoAPath,
439
+ video_c_path: currentVideoCPath
440
+ })
441
+ });
442
+
443
+ if (!res.ok) throw new Error(await res.text());
444
+
445
+ const data = await res.json();
446
+ const jobId = data.job_id;
447
+
448
+ // Poll Status
449
+ const poll = setInterval(async () => {
450
+ const statusRes = await fetch(`/status/${jobId}?t=${Date.now()}`);
451
+ if (statusRes.ok) {
452
+ const status = await statusRes.json();
453
+
454
+ if (status.status === "completed") {
455
+ clearInterval(poll);
456
+
457
+ // Render Video (Object-Contain prevents zoom overlap)
458
+ const bridgeContent = document.getElementById("bridge-content");
459
+ bridgeContent.innerHTML = `
460
+ <video controls autoplay loop class="w-full h-full object-contain bg-black">
461
+ <source src="${status.video_url}" type="video/mp4">
462
+ </video>
463
+ `;
464
+
465
+ // Activate Outer Glow
466
+ const outer = document.getElementById("bridge-card-outer");
467
+ outer.classList.remove("border-primary/20");
468
+ outer.classList.add("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
469
+
470
+ // Activate Inner Overlay Border
471
+ const border = document.getElementById("bridge-border");
472
+ border.classList.remove("border-transparent");
473
+ border.classList.add("border-primary/50");
474
+
475
+ btn.innerHTML = `<span class="material-symbols-outlined">check_circle</span> Done!`;
476
+ setTimeout(() => {
477
+ btn.innerHTML = originalText;
478
+ btn.disabled = false;
479
+ btn.classList.remove("opacity-50");
480
+ }, 3000);
481
+
482
+ } else if (status.status === "error") {
483
+ clearInterval(poll);
484
+ alert("Generation Error: " + status.log);
485
+ btn.innerHTML = originalText;
486
+ btn.disabled = false;
487
+ btn.classList.remove("opacity-50");
488
+ } else {
489
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> ${status.log} (${status.progress}%)`;
490
+ }
491
+ }
492
+ }, 1500);
493
+
494
+ } catch (err) {
495
+ alert("Request Error: " + err.message);
496
+ btn.innerHTML = originalText;
497
+ btn.disabled = false;
498
+ btn.classList.remove("opacity-50");
499
+ }
500
+ });
501
+ </script>
502
  </body>
503
 
504
  </html>