Deepfake Authenticator commited on
Commit
bb8ac90
Β·
1 Parent(s): da69892

feat: cinematic UI redesign

Browse files

- Animated particle canvas background (60 connected floating dots)
- Glassmorphism cards with backdrop blur
- Space Grotesk + JetBrains Mono typography
- Animated SVG eye logo with rotating scan arc
- Orbiting ring animation on upload icon
- Shimmer sweep on active analyze button
- Radar sonar loading animation (3 rings + sweep)
- Glitch text animation on FAKE verdict
- Pulsing verdict badge (red/green)
- Gradient confidence bar with glow
- Risk pill badge (LOW/MEDIUM/CRITICAL)
- Slide-in insight cards with colored left border
- Frame timeline with hover tooltips
- Hero section with gradient headline + stat pills

Files changed (2) hide show
  1. frontend/index.html +315 -490
  2. frontend/script.js +197 -144
frontend/index.html CHANGED
@@ -1,552 +1,377 @@
1
- <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Deepfake Authenticator</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
9
- <style>
10
- :root {
11
- --neon: #00ff88;
12
- --neon-dim:#00cc6a;
13
- --red: #ff4444;
14
- --red-dim: #cc2222;
15
- --bg: #080810;
16
- --card: #0f0f1a;
17
- --card2: #13131f;
18
- --border: #1e1e30;
19
- --text: #e2e8f0;
20
- --muted: #64748b;
21
- }
22
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
23
-
24
- body {
25
- background: var(--bg);
26
- font-family: 'JetBrains Mono', monospace;
27
- color: var(--text);
28
- min-height: 100vh;
29
- background-image:
30
- radial-gradient(ellipse 80% 50% at 50% -20%, #00ff8808 0%, transparent 60%),
31
- linear-gradient(var(--border) 1px, transparent 1px),
32
- linear-gradient(90deg, var(--border) 1px, transparent 1px);
33
- background-size: 100% 100%, 44px 44px, 44px 44px;
34
- }
35
-
36
- /* ── Scrollbar ── */
37
- ::-webkit-scrollbar { width: 4px; height: 4px; }
38
- ::-webkit-scrollbar-track { background: var(--bg); }
39
- ::-webkit-scrollbar-thumb { background: #00ff8833; border-radius: 4px; }
40
-
41
- /* ── Utilities ── */
42
- .neon { color: var(--neon); text-shadow: 0 0 12px #00ff8855; }
43
- .red { color: var(--red); text-shadow: 0 0 12px #ff444455; }
44
- .hidden { display: none !important; }
45
-
46
- /* ── Scanner ── */
47
- .scanner {
48
- position: fixed; inset: 0; pointer-events: none; z-index: 100;
49
- background: linear-gradient(to bottom,
50
- transparent 0%, transparent 49.9%,
51
- #00ff8806 50%, transparent 50.1%);
52
- background-size: 100% 4px;
53
- animation: scanMove 8s linear infinite;
54
- }
55
- @keyframes scanMove {
56
- 0% { background-position: 0 0; }
57
- 100% { background-position: 0 100vh; }
58
- }
59
-
60
- /* ── Header ── */
61
- .header {
62
- position: sticky; top: 0; z-index: 50;
63
- background: #080810ee;
64
- backdrop-filter: blur(12px);
65
- border-bottom: 1px solid var(--border);
66
- }
67
-
68
- /* ── Cards ── */
69
- .card {
70
- background: var(--card);
71
- border: 1px solid var(--border);
72
- border-radius: 14px;
73
- }
74
- .card-inner {
75
- background: var(--card2);
76
- border: 1px solid var(--border);
77
- border-radius: 10px;
78
- }
79
-
80
- /* ── Upload zone ── */
81
- #dropZone {
82
- border: 2px dashed #00ff8830;
83
- border-radius: 12px;
84
- cursor: pointer;
85
- transition: all .25s ease;
86
- min-height: 160px;
87
- display: flex; align-items: center; justify-content: center;
88
- }
89
- #dropZone:hover, #dropZone.drag-over {
90
- border-color: var(--neon);
91
- background: #00ff8806;
92
- box-shadow: 0 0 40px #00ff8812, inset 0 0 40px #00ff8806;
93
- }
94
-
95
- /* ── Analyze button ── */
96
- #analyzeBtn {
97
- background: transparent;
98
- border: 1px solid #00ff8830;
99
- color: #00ff8855;
100
- border-radius: 8px;
101
- padding: 12px 36px;
102
- font-family: inherit;
103
- font-weight: 700;
104
- font-size: 13px;
105
- letter-spacing: .15em;
106
- cursor: not-allowed;
107
- transition: all .25s ease;
108
- }
109
- #analyzeBtn.active {
110
- border-color: var(--neon);
111
- color: var(--neon);
112
- cursor: pointer;
113
- box-shadow: 0 0 20px #00ff8820;
114
- }
115
- #analyzeBtn.active:hover {
116
- background: #00ff8810;
117
- box-shadow: 0 0 30px #00ff8835;
118
- transform: translateY(-1px);
119
- }
120
- #analyzeBtn:disabled { opacity: .45; cursor: not-allowed; transform: none !important; }
121
-
122
- /* ── Progress bar ── */
123
- .prog-track {
124
- height: 3px; background: var(--border); border-radius: 99px; overflow: hidden;
125
- }
126
- .prog-fill {
127
- height: 100%; border-radius: 99px;
128
- background: linear-gradient(90deg, var(--neon), var(--neon-dim));
129
- box-shadow: 0 0 8px var(--neon);
130
- transition: width .4s ease;
131
- }
132
-
133
- /* ── Verdict card ── */
134
- #verdictCard {
135
- border-radius: 14px;
136
- border: 2px solid var(--border);
137
- background: var(--card);
138
- transition: border-color .5s, box-shadow .5s;
139
- }
140
- #verdictCard.fake {
141
- border-color: var(--red);
142
- box-shadow: 0 0 50px #ff444418, inset 0 0 60px #ff44440a;
143
- }
144
- #verdictCard.real {
145
- border-color: var(--neon);
146
- box-shadow: 0 0 50px #00ff8818, inset 0 0 60px #00ff880a;
147
- }
148
-
149
- /* ── Verdict badge ── */
150
- .verdict-badge {
151
- width: 96px; height: 96px; border-radius: 50%;
152
- border: 2px solid var(--border);
153
- display: flex; align-items: center; justify-content: center;
154
- font-size: 2.5rem;
155
- transition: all .5s;
156
- }
157
- .verdict-badge.fake {
158
- border-color: var(--red);
159
- box-shadow: 0 0 30px #ff444440;
160
- animation: pulseFake 2s ease-in-out infinite;
161
- }
162
- .verdict-badge.real {
163
- border-color: var(--neon);
164
- box-shadow: 0 0 30px #00ff8840;
165
- animation: pulseReal 2s ease-in-out infinite;
166
- }
167
- @keyframes pulseFake {
168
- 0%,100% { box-shadow: 0 0 20px #ff444430; }
169
- 50% { box-shadow: 0 0 45px #ff444460; }
170
- }
171
- @keyframes pulseReal {
172
- 0%,100% { box-shadow: 0 0 20px #00ff8830; }
173
- 50% { box-shadow: 0 0 45px #00ff8860; }
174
- }
175
-
176
- /* ── Confidence bar ── */
177
- .conf-track {
178
- height: 10px; background: var(--border); border-radius: 99px; overflow: hidden;
179
- }
180
- .conf-fill {
181
- height: 100%; border-radius: 99px;
182
- transition: width 1.2s cubic-bezier(.22,1,.36,1);
183
- }
184
- .conf-fill.fake {
185
- background: linear-gradient(90deg, #ff2222, #ff6666);
186
- box-shadow: 0 0 12px #ff444466;
187
- }
188
- .conf-fill.real {
189
- background: linear-gradient(90deg, var(--neon), var(--neon-dim));
190
- box-shadow: 0 0 12px #00ff8866;
191
- }
192
-
193
- /* ── Risk meter ── */
194
- .risk-meter {
195
- position: relative; height: 8px; border-radius: 99px; overflow: hidden;
196
- background: linear-gradient(90deg, #00ff88, #ffcc00, #ff4444);
197
- }
198
- .risk-needle {
199
- position: absolute; top: -4px;
200
- width: 3px; height: 16px; border-radius: 2px;
201
- background: white;
202
- box-shadow: 0 0 6px white;
203
- transition: left 1.2s cubic-bezier(.22,1,.36,1);
204
- transform: translateX(-50%);
205
- }
206
-
207
- /* ── Insight items ── */
208
- .insight-item {
209
- display: flex; align-items: flex-start; gap: 10px;
210
- padding: 10px 12px; border-radius: 8px;
211
- background: var(--card2);
212
- border: 1px solid var(--border);
213
- font-size: 12px; line-height: 1.5; color: #94a3b8;
214
- animation: slideIn .3s ease both;
215
- }
216
- .insight-item .dot {
217
- flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; margin-top: 5px;
218
- }
219
- @keyframes slideIn {
220
- from { opacity:0; transform: translateX(-8px); }
221
- to { opacity:1; transform: none; }
222
- }
223
-
224
- /* ── Meta rows ── */
225
- .meta-row {
226
- display: flex; justify-content: space-between; align-items: center;
227
- padding: 7px 0;
228
- border-bottom: 1px solid var(--border);
229
- font-size: 12px;
230
- }
231
- .meta-row:last-child { border-bottom: none; }
232
- .meta-key { color: var(--muted); letter-spacing: .05em; }
233
- .meta-val { color: var(--text); font-weight: 600; }
234
-
235
- /* ── Timeline ── */
236
- #timelineChart {
237
- display: flex; align-items: flex-end; gap: 3px;
238
- height: 80px; overflow-x: auto; padding-bottom: 2px;
239
- position: relative;
240
- }
241
- .t-bar {
242
- flex-shrink: 0; width: 10px; border-radius: 3px 3px 0 0;
243
- transition: opacity .3s, transform .2s;
244
- cursor: pointer;
245
- position: relative;
246
- }
247
- .t-bar:hover { transform: scaleY(1.08); filter: brightness(1.3); }
248
- .t-bar .tooltip {
249
- display: none; position: absolute; bottom: calc(100% + 6px); left: 50%;
250
- transform: translateX(-50%);
251
- background: #1e1e30; border: 1px solid var(--border);
252
- color: var(--text); font-size: 10px; padding: 4px 7px; border-radius: 5px;
253
- white-space: nowrap; z-index: 10; pointer-events: none;
254
- }
255
- .t-bar:hover .tooltip { display: block; }
256
- .threshold-line {
257
- position: absolute; left: 0; right: 0; height: 1px;
258
- background: #facc1555; pointer-events: none;
259
- }
260
-
261
- /* ── Agent steps ── */
262
- .agent-card {
263
- border-radius: 10px; padding: 12px; text-align: center;
264
- background: var(--card2); border: 1px solid var(--border);
265
- transition: border-color .3s, background .3s, box-shadow .3s;
266
- }
267
- .agent-card.active {
268
- border-color: #00ff8844;
269
- background: #00ff8808;
270
- box-shadow: 0 0 20px #00ff8810;
271
- }
272
- .agent-card .a-status { font-size: 10px; color: var(--muted); margin-top: 4px; }
273
- .agent-card.active .a-status { color: var(--neon); }
274
-
275
- /* ── Fade/slide animations ── */
276
- .fade-up {
277
- animation: fadeUp .45s ease both;
278
- }
279
- @keyframes fadeUp {
280
- from { opacity:0; transform: translateY(16px); }
281
- to { opacity:1; transform: none; }
282
- }
283
-
284
- /* ── Section label ── */
285
- .sec-label {
286
- font-size: 10px; font-weight: 700; letter-spacing: .12em;
287
- color: var(--muted); display: flex; align-items: center; gap: 6px;
288
- }
289
- .sec-label::before {
290
- content: ''; display: inline-block;
291
- width: 6px; height: 6px; border-radius: 50%;
292
- background: var(--neon); box-shadow: 0 0 6px var(--neon);
293
- }
294
-
295
- /* ── Glow badge ── */
296
- #modelBadge {
297
- font-size: 10px; padding: 4px 12px; border-radius: 99px;
298
- border: 1px solid var(--border); color: var(--muted);
299
- letter-spacing: .08em; transition: all .3s;
300
- }
301
- #modelBadge.ready {
302
- border-color: #00ff8844; color: #00ff88aa;
303
- box-shadow: 0 0 10px #00ff8815;
304
- }
305
- </style>
306
  </head>
307
  <body>
308
 
309
- <div class="scanner"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- <!-- ══ HEADER ══════════════════════════════════════════ -->
312
- <header class="header">
313
- <div style="max-width:900px; margin:0 auto; padding:14px 24px; display:flex; align-items:center; justify-content:space-between;">
314
- <div style="display:flex; align-items:center; gap:12px;">
315
- <div style="width:34px;height:34px;border-radius:8px;border:1px solid #00ff8855;
316
- display:flex;align-items:center;justify-content:center;
317
- box-shadow:0 0 14px #00ff8820;">
318
- <svg width="16" height="16" fill="none" stroke="#00ff88" stroke-width="2" viewBox="0 0 24 24">
319
- <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
320
- <path d="M11 8v6M8 11h6"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  </svg>
322
  </div>
323
  <div>
324
- <div style="font-size:15px;font-weight:700;letter-spacing:.12em;" class="neon">DEEPFAKE AUTHENTICATOR</div>
325
- <div style="font-size:10px;color:var(--muted);letter-spacing:.1em;">AI-POWERED VIDEO FORENSICS</div>
326
  </div>
327
  </div>
328
- <div id="modelBadge">CONNECTING...</div>
 
 
 
329
  </div>
330
  </header>
331
 
332
- <!-- ══ MAIN ════════════════════════════════════════════ -->
333
- <main style="max-width:900px; margin:0 auto; padding:32px 24px; display:flex; flex-direction:column; gap:20px;">
 
 
 
 
 
 
 
 
 
 
 
334
 
335
- <!-- Upload card -->
336
- <div class="card" style="padding:20px;">
337
  <div id="dropZone">
338
- <div id="uploadPrompt" style="text-align:center; padding:20px;">
339
- <div style="width:56px;height:56px;border-radius:50%;border:1px solid #00ff8830;
340
- display:flex;align-items:center;justify-content:center;margin:0 auto 14px;">
341
- <svg width="24" height="24" fill="none" stroke="#00ff8866" stroke-width="1.5" viewBox="0 0 24 24">
342
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
343
- <polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
344
- </svg>
 
 
 
345
  </div>
346
- <p style="font-size:14px;font-weight:600;color:#cbd5e1;margin-bottom:6px;">Drop video here or click to upload</p>
347
- <p style="font-size:11px;color:var(--muted);">MP4 Β· AVI Β· MOV Β· MKV Β· WebM &nbsp;Β·&nbsp; Max 100 MB</p>
 
348
  </div>
349
-
350
- <div id="fileChosen" class="hidden" style="display:flex;align-items:center;gap:14px;padding:20px;">
351
- <div style="width:44px;height:44px;border-radius:8px;background:#00ff8810;border:1px solid #00ff8830;
352
- display:flex;align-items:center;justify-content:center;flex-shrink:0;">
353
- <svg width="20" height="20" fill="none" stroke="#00ff88" stroke-width="2" viewBox="0 0 24 24">
354
- <rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"/>
355
- <line x1="7" y1="2" x2="7" y2="22"/><line x1="17" y1="2" x2="17" y2="22"/>
356
- <line x1="2" y1="12" x2="22" y2="12"/><line x1="2" y1="7" x2="7" y2="7"/>
357
- <line x1="2" y1="17" x2="7" y2="17"/><line x1="17" y1="17" x2="22" y2="17"/>
358
- <line x1="17" y1="7" x2="22" y2="7"/>
359
  </svg>
360
  </div>
361
  <div style="flex:1;min-width:0;">
362
- <p id="chosenName" style="font-size:13px;font-weight:600;color:#e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"></p>
363
- <p id="chosenSize" style="font-size:11px;color:var(--muted);margin-top:2px;"></p>
364
  </div>
365
- <button id="clearBtn" type="button"
366
- style="background:none;border:none;color:var(--muted);cursor:pointer;padding:6px;border-radius:6px;transition:color .2s;"
367
- onmouseover="this.style.color='#ff4444'" onmouseout="this.style.color='var(--muted)'">
368
- <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
369
- <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
370
- </svg>
371
  </button>
372
  </div>
373
  </div>
374
-
375
- <input type="file" id="fileInput" accept="video/*" style="display:none;" />
376
-
377
- <div style="margin-top:16px;text-align:center;">
378
- <button id="analyzeBtn" type="button" disabled onclick="analyzeVideo()">
379
- β–Ά&nbsp;&nbsp;ANALYZE VIDEO
380
  </button>
381
  </div>
382
- </div>
383
-
384
- <!-- Loading -->
385
- <div id="loadingSection" class="hidden card fade-up" style="padding:24px;">
386
- <div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;">
387
- <div style="width:8px;height:8px;border-radius:50%;background:var(--neon);
388
- box-shadow:0 0 8px var(--neon);animation:ping 1s ease-in-out infinite;"></div>
389
- <span id="loadingStatus" class="neon" style="font-size:12px;font-weight:700;letter-spacing:.1em;">INITIALIZING...</span>
 
 
 
390
  </div>
391
- <div class="prog-track" style="margin-bottom:20px;">
392
- <div id="progressBar" class="prog-fill" style="width:0%;"></div>
 
 
 
 
 
393
  </div>
394
- <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;">
395
- <div class="agent-card" id="ag0">
396
- <div style="font-size:20px;margin-bottom:4px;">🎬</div>
397
- <div style="font-size:10px;color:var(--muted);font-weight:600;">FRAME ANALYZER</div>
398
- <div class="a-status">Waiting...</div>
 
 
 
399
  </div>
400
- <div class="agent-card" id="ag1">
401
- <div style="font-size:20px;margin-bottom:4px;">πŸ‘€</div>
402
- <div style="font-size:10px;color:var(--muted);font-weight:600;">FACE DETECTOR</div>
403
- <div class="a-status">Waiting...</div>
 
 
404
  </div>
405
- <div class="agent-card" id="ag2">
406
- <div style="font-size:20px;margin-bottom:4px;">🧠</div>
407
- <div style="font-size:10px;color:var(--muted);font-weight:600;">DECISION AGENT</div>
408
- <div class="a-status">Waiting...</div>
 
 
409
  </div>
410
- <div class="agent-card" id="ag3">
411
- <div style="font-size:20px;margin-bottom:4px;">πŸ“Š</div>
412
- <div style="font-size:10px;color:var(--muted);font-weight:600;">REPORT GEN</div>
413
- <div class="a-status">Waiting...</div>
 
 
414
  </div>
415
  </div>
416
- </div>
417
 
418
- <!-- Result -->
419
- <div id="resultSection" class="hidden fade-up" style="display:flex;flex-direction:column;gap:16px;">
420
 
421
- <!-- Verdict -->
422
- <div id="verdictCard" style="padding:28px 32px;">
423
- <div style="display:flex;flex-wrap:wrap;align-items:center;gap:28px;">
424
-
425
- <!-- Badge + label -->
426
- <div style="text-align:center;flex-shrink:0;">
427
- <div id="verdictBadge" class="verdict-badge" style="margin:0 auto 10px;">
428
- <span id="verdictEmoji"></span>
429
- </div>
430
- <div id="verdictLabel" style="font-size:28px;font-weight:700;letter-spacing:.15em;"></div>
431
- <div style="font-size:10px;color:var(--muted);letter-spacing:.1em;margin-top:3px;">VERDICT</div>
432
  </div>
433
-
434
- <!-- Confidence + risk -->
435
- <div style="flex:1;min-width:220px;">
436
- <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px;">
437
- <span style="font-size:11px;color:var(--muted);letter-spacing:.08em;">CONFIDENCE SCORE</span>
438
- <span id="confValue" style="font-size:32px;font-weight:700;"></span>
439
- </div>
440
- <div class="conf-track" style="margin-bottom:18px;">
441
- <div id="confBar" class="conf-fill" style="width:0%;"></div>
442
- </div>
443
-
444
- <!-- Risk meter -->
445
- <div style="margin-bottom:6px;display:flex;justify-content:space-between;align-items:center;">
446
- <span style="font-size:10px;color:var(--muted);letter-spacing:.08em;">RISK LEVEL</span>
447
- <span id="riskLabel" style="font-size:10px;font-weight:700;letter-spacing:.08em;"></span>
448
- </div>
449
- <div class="risk-meter">
450
- <div id="riskNeedle" class="risk-needle" style="left:50%;"></div>
451
- </div>
452
- <div style="display:flex;justify-content:space-between;margin-top:4px;">
453
- <span style="font-size:9px;color:var(--muted);">LOW</span>
454
- <span style="font-size:9px;color:var(--muted);">MEDIUM</span>
455
- <span style="font-size:9px;color:var(--muted);">HIGH</span>
456
- </div>
457
  </div>
458
  </div>
459
  </div>
460
 
461
  <!-- Insights + Meta -->
462
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
463
-
464
- <div class="card" style="padding:18px;">
465
- <div class="sec-label" style="margin-bottom:14px;">ANALYSIS INSIGHTS</div>
466
- <div id="detailsList" style="display:flex;flex-direction:column;gap:8px;"></div>
467
  </div>
468
-
469
- <div class="card" style="padding:18px;">
470
- <div class="sec-label" style="margin-bottom:14px;">VIDEO METADATA</div>
471
- <div id="metaGrid"></div>
472
-
473
- <!-- Model info -->
474
- <div style="margin-top:14px;padding-top:12px;border-top:1px solid var(--border);">
475
- <div class="sec-label" style="margin-bottom:10px;">DETECTION ENGINE</div>
476
- <div style="font-size:10px;color:var(--muted);line-height:1.8;">
477
- <div>πŸ”¬ dima806/deepfake_vs_real (99.3% acc)</div>
478
- <div>πŸ”¬ prithivMLmods/Detector-v2 (92.1% acc)</div>
479
- <div style="margin-top:4px;color:#00ff8877;">⚑ Weighted ensemble · ViT architecture</div>
480
- </div>
481
  </div>
482
  </div>
483
  </div>
484
 
485
  <!-- Frame Timeline -->
486
- <div class="card" style="padding:18px;">
487
- <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;flex-wrap:wrap;gap:8px;">
488
- <div class="sec-label">FRAME-BY-FRAME TIMELINE</div>
489
- <div style="display:flex;align-items:center;gap:14px;font-size:10px;color:var(--muted);">
490
- <span style="display:flex;align-items:center;gap:5px;">
491
- <span style="width:10px;height:10px;border-radius:2px;background:var(--neon);display:inline-block;"></span>REAL
492
- </span>
493
- <span style="display:flex;align-items:center;gap:5px;">
494
- <span style="width:10px;height:10px;border-radius:2px;background:var(--red);display:inline-block;"></span>FAKE
495
- </span>
496
- <span style="display:flex;align-items:center;gap:5px;">
497
- <span style="width:20px;height:1px;background:#facc1566;display:inline-block;"></span>60% THRESHOLD
498
- </span>
499
- </div>
500
  </div>
501
- <div id="timelineChart"></div>
502
  </div>
503
 
504
- <div style="text-align:center;">
505
- <button onclick="resetAll()" type="button"
506
- style="background:none;border:1px solid var(--border);color:var(--muted);
507
- font-family:inherit;font-size:12px;letter-spacing:.1em;padding:10px 24px;
508
- border-radius:8px;cursor:pointer;transition:all .2s;"
509
- onmouseover="this.style.borderColor='#00ff8844';this.style.color='var(--neon)'"
510
- onmouseout="this.style.borderColor='var(--border)';this.style.color='var(--muted)'">
511
- β†Ί &nbsp;ANALYZE ANOTHER VIDEO
512
  </button>
513
  </div>
514
- </div>
515
 
516
- <!-- Error -->
517
- <div id="errorSection" class="hidden card fade-up"
518
- style="padding:20px;border-color:#ff444433;background:#ff44440a;">
519
- <div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;">
520
- <span style="font-size:18px;">⚠️</span>
521
- <span style="color:var(--red);font-weight:700;font-size:13px;letter-spacing:.08em;">ANALYSIS FAILED</span>
522
  </div>
523
- <p id="errorMsg" style="font-size:12px;color:var(--muted);line-height:1.6;"></p>
524
- <button onclick="resetAll()" type="button"
525
- style="margin-top:12px;background:none;border:none;color:var(--muted);
526
- font-family:inherit;font-size:11px;cursor:pointer;letter-spacing:.08em;"
527
- onmouseover="this.style.color='var(--neon)'" onmouseout="this.style.color='var(--muted)'">
528
- β†Ί Try again
529
- </button>
530
- </div>
531
 
532
- </main>
533
 
534
- <footer style="border-top:1px solid var(--border);padding:20px;text-align:center;
535
- font-size:10px;color:#334155;letter-spacing:.12em;margin-top:20px;">
536
- DEEPFAKE AUTHENTICATOR &nbsp;Β·&nbsp; MEDIAPIPE + ENSEMBLE ViT &nbsp;Β·&nbsp; LOCAL INFERENCE
537
  </footer>
538
 
539
- <style>
540
- @keyframes ping {
541
- 0%,100% { transform:scale(1); opacity:1; }
542
- 50% { transform:scale(1.5); opacity:.5; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  }
544
- @media (max-width: 640px) {
545
- #verdictCard > div { flex-direction: column !important; }
546
- #resultSection > div:nth-child(2) { grid-template-columns: 1fr !important; }
547
- }
548
- </style>
549
-
550
  <script src="script.js"></script>
551
  </body>
552
- </html>
 
1
+ ο»Ώ<!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1.0"/>
6
+ <title>Deepfake Authenticator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet"/>
9
+ <style>
10
+ :root{--g:#00ff88;--r:#ff3355;--w:#ffaa00;--bg:#050508;--card:rgba(255,255,255,0.04);--border:rgba(0,255,136,0.18);--muted:#5a6a7a;}
11
+ *{box-sizing:border-box;margin:0;padding:0;}
12
+ body{background:var(--bg);font-family:'Space Grotesk',sans-serif;color:#e2e8f0;min-height:100vh;overflow-x:hidden;}
13
+ code,pre,.mono{font-family:'JetBrains Mono',monospace;}
14
+ ::-webkit-scrollbar{width:5px;} ::-webkit-scrollbar-track{background:var(--bg);} ::-webkit-scrollbar-thumb{background:rgba(0,255,136,0.25);border-radius:4px;}
15
+ .hidden{display:none!important;}
16
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </head>
18
  <body>
19
 
20
+ <style>
21
+ /* ── Particle Canvas ── */
22
+ #particles{position:fixed;inset:0;z-index:0;pointer-events:none;}
23
+ /* ── Grid overlay ── */
24
+ .grid-bg{position:fixed;inset:0;z-index:0;pointer-events:none;
25
+ background-image:linear-gradient(rgba(0,255,136,0.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,255,136,0.03) 1px,transparent 1px);
26
+ background-size:60px 60px;}
27
+ /* ── Glass ── */
28
+ .glass{background:var(--card);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:16px;box-shadow:0 8px 40px rgba(0,0,0,0.5);}
29
+ .glass-sm{background:rgba(255,255,255,0.03);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.07);border-radius:12px;}
30
+ /* ── Glow borders ── */
31
+ .glow-green{box-shadow:0 0 20px rgba(0,255,136,0.25),inset 0 0 20px rgba(0,255,136,0.04);}
32
+ .glow-red{box-shadow:0 0 20px rgba(255,51,85,0.25),inset 0 0 20px rgba(255,51,85,0.04);}
33
+ /* ── Header ── */
34
+ header{position:sticky;top:0;z-index:50;background:rgba(5,5,8,0.85);backdrop-filter:blur(24px);border-bottom:1px solid rgba(0,255,136,0.1);}
35
+ /* ── Eye SVG animation ── */
36
+ @keyframes scanPulse{0%,100%{opacity:1;transform:scale(1);}50%{opacity:0.5;transform:scale(1.15);}}
37
+ @keyframes eyeScan{0%{stroke-dashoffset:100;}100%{stroke-dashoffset:0;}}
38
+ .eye-scan{animation:scanPulse 2.5s ease-in-out infinite;}
39
+ /* ── Status dot ── */
40
+ @keyframes statusBlink{0%,100%{opacity:1;box-shadow:0 0 6px var(--g);}50%{opacity:0.4;box-shadow:none;}}
41
+ .status-dot{width:8px;height:8px;border-radius:50%;background:var(--g);animation:statusBlink 2s ease-in-out infinite;}
42
+ /* ── Drop zone border animation ── */
43
+ @keyframes borderDash{to{stroke-dashoffset:-20;}}
44
+ #dropZone{border:2px dashed rgba(0,255,136,0.25);border-radius:16px;cursor:pointer;min-height:200px;display:flex;align-items:center;justify-content:center;transition:all 0.35s ease;position:relative;overflow:hidden;}
45
+ #dropZone:hover,#dropZone.drag-over{border-color:var(--g);background:rgba(0,255,136,0.04);box-shadow:0 0 40px rgba(0,255,136,0.2),inset 0 0 30px rgba(0,255,136,0.06);transform:scale(1.01);}
46
+ /* ── Orbit ring ── */
47
+ @keyframes orbitRing{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
48
+ .orbit{animation:orbitRing 3s linear infinite;transform-origin:center;}
49
+ /* ── Analyze button ── */
50
+ #analyzeBtn{width:100%;padding:16px;border-radius:12px;font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:14px;letter-spacing:0.12em;text-transform:uppercase;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05);color:var(--muted);cursor:not-allowed;transition:all 0.3s ease;position:relative;overflow:hidden;}
51
+ #analyzeBtn.active{background:linear-gradient(135deg,#00ff88,#00cc66);color:#050508;border-color:transparent;cursor:pointer;box-shadow:0 0 30px rgba(0,255,136,0.4);}
52
+ #analyzeBtn.active:hover{transform:translateY(-2px) scale(1.01);box-shadow:0 0 50px rgba(0,255,136,0.6);}
53
+ #analyzeBtn.active::after{content:'';position:absolute;top:0;left:-100%;width:60%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.25),transparent);animation:shimmer 2s ease infinite;}
54
+ @keyframes shimmer{to{left:150%;}}
55
+ #analyzeBtn:disabled{opacity:0.45;cursor:not-allowed;transform:none!important;}
56
+ </style>
57
 
58
+ <style>
59
+ /* ── Loading ── */
60
+ @keyframes radarSpin{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
61
+ @keyframes radarPulse{0%,100%{opacity:0.8;transform:scale(1);}50%{opacity:0.3;transform:scale(1.05);}}
62
+ .radar-ring{border-radius:50%;border:1px solid rgba(0,255,136,0.2);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}
63
+ .radar-sweep{position:absolute;top:50%;left:50%;width:50%;height:2px;transform-origin:left center;background:linear-gradient(90deg,transparent,var(--g));animation:radarSpin 2s linear infinite;}
64
+ /* ── Agent cards ── */
65
+ .agent-card{transition:all 0.4s ease;border:1px solid rgba(255,255,255,0.06);border-radius:12px;padding:12px 16px;background:rgba(255,255,255,0.02);}
66
+ .agent-card.active{border-color:var(--g);background:rgba(0,255,136,0.06);box-shadow:0 0 20px rgba(0,255,136,0.15);}
67
+ .agent-card.active .agent-dot{background:var(--g);box-shadow:0 0 8px var(--g);}
68
+ .agent-dot{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,0.15);transition:all 0.4s ease;flex-shrink:0;}
69
+ /* ── Progress bar ── */
70
+ .prog-track{height:4px;background:rgba(255,255,255,0.06);border-radius:99px;overflow:hidden;}
71
+ .prog-fill{height:100%;border-radius:99px;background:linear-gradient(90deg,#00aaff,var(--g));box-shadow:0 0 12px var(--g);transition:width 0.5s ease;}
72
+ /* ── Verdict ── */
73
+ @keyframes glitch{0%,100%{text-shadow:0 0 10px var(--r);}20%{text-shadow:-2px 0 #ff0,2px 0 #0ff,0 0 20px var(--r);}40%{text-shadow:2px 0 #ff0,-2px 0 #0ff,0 0 10px var(--r);}60%{text-shadow:0 0 30px var(--r);}}
74
+ .glitch-text{animation:glitch 3s ease-in-out infinite;}
75
+ @keyframes fadeUp{from{opacity:0;transform:translateY(24px);}to{opacity:1;transform:none;}}
76
+ .fade-up{animation:fadeUp 0.6s cubic-bezier(0.16,1,0.3,1) both;}
77
+ @keyframes slideInLeft{from{opacity:0;transform:translateX(-20px);}to{opacity:1;transform:none;}}
78
+ /* ── Verdict badge ── */
79
+ .verdict-badge{width:110px;height:110px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:2.8rem;transition:all 0.5s;border:2px solid var(--border);}
80
+ @keyframes pulseFake{0%,100%{box-shadow:0 0 20px rgba(255,51,85,0.3);}50%{box-shadow:0 0 50px rgba(255,51,85,0.7);}}
81
+ @keyframes pulseReal{0%,100%{box-shadow:0 0 20px rgba(0,255,136,0.3);}50%{box-shadow:0 0 50px rgba(0,255,136,0.7);}}
82
+ .verdict-badge.fake{border-color:var(--r);animation:pulseFake 2s infinite;}
83
+ .verdict-badge.real{border-color:var(--g);animation:pulseReal 2s infinite;}
84
+ /* ── Confidence arc (CSS semicircle gauge) ── */
85
+ .gauge-wrap{position:relative;width:200px;height:110px;overflow:hidden;margin:0 auto;}
86
+ .gauge-bg{width:200px;height:200px;border-radius:50%;border:12px solid rgba(255,255,255,0.06);position:absolute;top:0;left:0;clip-path:inset(0 0 50% 0);}
87
+ .gauge-fill{width:200px;height:200px;border-radius:50%;border:12px solid transparent;position:absolute;top:0;left:0;clip-path:inset(0 0 50% 0);transition:transform 1.2s cubic-bezier(0.22,1,0.36,1);}
88
+ .gauge-fill.fake{border-color:var(--r);box-shadow:0 0 20px rgba(255,51,85,0.5);}
89
+ .gauge-fill.real{border-color:var(--g);box-shadow:0 0 20px rgba(0,255,136,0.5);}
90
+ /* ── Conf bar ── */
91
+ .conf-track{height:10px;background:rgba(255,255,255,0.05);border-radius:99px;overflow:hidden;border:1px solid rgba(255,255,255,0.05);}
92
+ .conf-fill{height:100%;border-radius:99px;transition:width 1.2s cubic-bezier(0.22,1,0.36,1);}
93
+ .conf-fill.fake{background:linear-gradient(90deg,#880022,var(--r));box-shadow:0 0 15px rgba(255,51,85,0.6);}
94
+ .conf-fill.real{background:linear-gradient(90deg,#00aaff,var(--g));box-shadow:0 0 15px rgba(0,255,136,0.6);}
95
+ /* ── Risk needle ── */
96
+ #riskNeedle{display:inline-block;padding:4px 14px;border-radius:99px;font-size:11px;font-weight:700;letter-spacing:0.15em;border:1px solid;transition:all 0.5s;}
97
+ /* ── Insight bullets ── */
98
+ .insight-item{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:10px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.05);font-size:13px;line-height:1.6;color:#a0aec0;animation:slideInLeft 0.4s ease both;}
99
+ .insight-item:hover{background:rgba(255,255,255,0.04);}
100
+ .insight-dot{flex-shrink:0;width:8px;height:8px;border-radius:50%;margin-top:6px;}
101
+ /* ── Timeline bars ── */
102
+ .bar-wrap{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;min-width:0;position:relative;}
103
+ .bar-outer{width:100%;background:rgba(255,255,255,0.05);border-radius:4px 4px 0 0;overflow:hidden;position:relative;}
104
+ .bar-inner{width:100%;border-radius:4px 4px 0 0;transition:height 0.8s cubic-bezier(0.22,1,0.36,1);}
105
+ .bar-wrap:hover .bar-tooltip{opacity:1;transform:translateY(-4px);}
106
+ .bar-tooltip{position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:rgba(10,10,20,0.95);border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:4px 8px;font-size:10px;white-space:nowrap;pointer-events:none;opacity:0;transition:all 0.2s ease;z-index:10;font-family:'JetBrains Mono',monospace;}
107
+ /* ── Meta grid ── */
108
+ .meta-row{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.05);}
109
+ .meta-row:last-child{border-bottom:none;}
110
+ /* ── Error ── */
111
+ #errorSection{border-color:rgba(255,51,85,0.3)!important;background:rgba(255,51,85,0.04)!important;}
112
+ /* ── Stat pills ── */
113
+ .stat-pill{display:inline-flex;align-items:center;gap:8px;padding:8px 18px;border-radius:99px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);font-size:12px;font-weight:600;letter-spacing:0.05em;color:#a0aec0;}
114
+ .stat-pill .pip{width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:0 0 6px var(--g);}
115
+ /* ── Section label ── */
116
+ .sec-label{font-size:10px;font-weight:700;letter-spacing:0.2em;text-transform:uppercase;color:#00aaff;display:flex;align-items:center;gap:8px;margin-bottom:14px;}
117
+ .sec-label::before{content:'';display:inline-block;width:3px;height:14px;background:#00aaff;border-radius:2px;box-shadow:0 0 8px #00aaff;}
118
+ /* ── Model badge ── */
119
+ #modelBadge{display:inline-flex;align-items:center;gap:8px;padding:6px 14px;border-radius:99px;background:rgba(0,255,136,0.08);border:1px solid rgba(0,255,136,0.25);font-size:11px;font-weight:600;letter-spacing:0.1em;color:var(--g);}
120
+ </style>
121
+
122
+
123
+ <!-- Particle canvas + grid -->
124
+ <canvas id="particles"></canvas>
125
+ <div class="grid-bg"></div>
126
+
127
+ <!-- ══ HEADER ══ -->
128
+ <header>
129
+ <div style="max-width:1100px;margin:0 auto;padding:0 24px;height:64px;display:flex;align-items:center;justify-content:space-between;">
130
+ <div style="display:flex;align-items:center;gap:14px;">
131
+ <!-- Animated eye logo -->
132
+ <div class="eye-scan" style="width:36px;height:36px;display:flex;align-items:center;justify-content:center;">
133
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none">
134
+ <ellipse cx="18" cy="18" rx="14" ry="9" stroke="#00ff88" stroke-width="1.5" opacity="0.9"/>
135
+ <circle cx="18" cy="18" r="5" fill="#00ff88" opacity="0.9"/>
136
+ <circle cx="18" cy="18" r="2.5" fill="#050508"/>
137
+ <path d="M4 18 Q18 4 32 18" stroke="#00ff88" stroke-width="0.8" stroke-dasharray="4 3" opacity="0.4">
138
+ <animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="8s" repeatCount="indefinite"/>
139
+ </path>
140
  </svg>
141
  </div>
142
  <div>
143
+ <div style="font-size:13px;font-weight:700;letter-spacing:0.18em;color:#fff;">DEEPFAKE AUTHENTICATOR</div>
144
+ <div style="font-size:9px;letter-spacing:0.25em;color:var(--muted);font-family:'JetBrains Mono',monospace;">AI-POWERED VIDEO FORENSICS</div>
145
  </div>
146
  </div>
147
+ <div id="modelBadge">
148
+ <div class="status-dot"></div>
149
+ <span>ViT ENSEMBLE</span>
150
+ </div>
151
  </div>
152
  </header>
153
 
154
+ <!-- ══ MAIN ══ -->
155
+ <div style="position:relative;z-index:1;max-width:860px;margin:0 auto;padding:48px 24px 80px;">
156
+
157
+ <!-- Hero -->
158
+ <div class="fade-up" style="text-align:center;margin-bottom:48px;animation-delay:0.1s;">
159
+ <h1 style="font-size:clamp(2rem,5vw,3.5rem);font-weight:700;line-height:1.1;margin-bottom:16px;background:linear-gradient(135deg,#fff 30%,var(--g));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">Is This Video Real?</h1>
160
+ <p style="color:var(--muted);font-size:15px;max-width:480px;margin:0 auto 28px;line-height:1.7;">Advanced AI ensemble detects facial manipulation with 99%+ accuracy using dual Vision Transformer models.</p>
161
+ <div style="display:flex;flex-wrap:wrap;gap:10px;justify-content:center;">
162
+ <div class="stat-pill"><span class="pip"></span>2 ViT Models</div>
163
+ <div class="stat-pill"><span class="pip"></span>40 Frame Analysis</div>
164
+ <div class="stat-pill"><span class="pip"></span>&lt; 15s Detection</div>
165
+ </div>
166
+ </div>
167
 
168
+ <!-- ══ UPLOAD SECTION ══ -->
169
+ <section id="uploadSection" class="glass fade-up" style="padding:32px;margin-bottom:24px;animation-delay:0.2s;">
170
  <div id="dropZone">
171
+ <!-- Upload prompt -->
172
+ <div id="uploadPrompt" style="text-align:center;padding:40px 24px;">
173
+ <div style="position:relative;width:80px;height:80px;margin:0 auto 20px;">
174
+ <div style="position:absolute;inset:0;border-radius:50%;border:1px dashed rgba(0,255,136,0.3);" class="orbit"></div>
175
+ <div style="position:absolute;inset:8px;border-radius:50%;border:1px solid rgba(0,255,136,0.15);"></div>
176
+ <div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;">
177
+ <svg width="32" height="32" fill="none" stroke="#00ff88" stroke-width="1.5" viewBox="0 0 24 24" opacity="0.8">
178
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/>
179
+ </svg>
180
+ </div>
181
  </div>
182
+ <h3 style="font-size:17px;font-weight:600;color:#fff;margin-bottom:8px;">Drop Video for Forensic Analysis</h3>
183
+ <p style="font-size:13px;color:var(--muted);">Drag &amp; drop or click to browse</p>
184
+ <p style="font-size:11px;color:var(--muted);margin-top:12px;opacity:0.6;font-family:'JetBrains Mono',monospace;">MP4 Β· AVI Β· MOV Β· MKV Β· WebM β€” Max 100MB</p>
185
  </div>
186
+ <!-- File chosen state -->
187
+ <div id="fileChosen" class="hidden" style="width:100%;padding:28px 32px;display:flex;align-items:center;gap:20px;">
188
+ <div style="width:56px;height:56px;border-radius:12px;background:rgba(0,255,136,0.1);border:1px solid rgba(0,255,136,0.3);display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 0 20px rgba(0,255,136,0.15);">
189
+ <svg width="28" height="28" fill="none" stroke="#00ff88" stroke-width="1.5" viewBox="0 0 24 24">
190
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/>
 
 
 
 
 
191
  </svg>
192
  </div>
193
  <div style="flex:1;min-width:0;">
194
+ <p id="chosenName" style="font-size:14px;font-weight:600;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></p>
195
+ <p id="chosenSize" style="font-size:12px;color:var(--muted);margin-top:4px;font-family:'JetBrains Mono',monospace;"></p>
196
  </div>
197
+ <button id="clearBtn" style="padding:8px;color:var(--muted);background:none;border:none;cursor:pointer;border-radius:8px;transition:all 0.2s;" onmouseover="this.style.color='#ff3355';this.style.background='rgba(255,51,85,0.1)'" onmouseout="this.style.color='var(--muted)';this.style.background='none'">
198
+ <svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
 
 
 
 
199
  </button>
200
  </div>
201
  </div>
202
+ <input type="file" id="fileInput" accept="video/*" style="display:none;"/>
203
+ <div style="margin-top:20px;">
204
+ <button id="analyzeBtn" disabled>
205
+ <span id="analyzeBtnText">Analyze Video</span>
 
 
206
  </button>
207
  </div>
208
+ </section>
209
+
210
+ <!-- ══ LOADING SECTION ══ -->
211
+ <section id="loadingSection" class="hidden glass fade-up" style="padding:48px 32px;text-align:center;margin-bottom:24px;">
212
+ <!-- Radar -->
213
+ <div style="position:relative;width:140px;height:140px;margin:0 auto 36px;">
214
+ <div class="radar-ring" style="width:140px;height:140px;"></div>
215
+ <div class="radar-ring" style="width:100px;height:100px;"></div>
216
+ <div class="radar-ring" style="width:60px;height:60px;"></div>
217
+ <div class="radar-sweep" style="width:70px;top:50%;left:50%;transform-origin:left center;"></div>
218
+ <div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:12px;height:12px;border-radius:50%;background:var(--g);box-shadow:0 0 15px var(--g);"></div>
219
  </div>
220
+ <!-- Status -->
221
+ <p id="loadingStatus" style="font-size:11px;font-weight:700;letter-spacing:0.2em;color:#00aaff;text-transform:uppercase;margin-bottom:20px;font-family:'JetBrains Mono',monospace;">Initializing sequence...</p>
222
+ <!-- Progress -->
223
+ <div style="max-width:400px;margin:0 auto 32px;">
224
+ <div class="prog-track">
225
+ <div id="progressBar" style="width:0%;" class="prog-fill"></div>
226
+ </div>
227
  </div>
228
+ <!-- Agent cards -->
229
+ <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:12px;max-width:500px;margin:0 auto;">
230
+ <div id="ag0" class="agent-card" style="display:flex;align-items:center;gap:10px;">
231
+ <div class="agent-dot"></div>
232
+ <div style="text-align:left;">
233
+ <div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">Frame Extractor</div>
234
+ <div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Sampling keyframes</div>
235
+ </div>
236
  </div>
237
+ <div id="ag1" class="agent-card" style="display:flex;align-items:center;gap:10px;">
238
+ <div class="agent-dot"></div>
239
+ <div style="text-align:left;">
240
+ <div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">Face Detector</div>
241
+ <div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Isolating regions</div>
242
+ </div>
243
  </div>
244
+ <div id="ag2" class="agent-card" style="display:flex;align-items:center;gap:10px;">
245
+ <div class="agent-dot"></div>
246
+ <div style="text-align:left;">
247
+ <div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">ViT Ensemble</div>
248
+ <div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Neural inference</div>
249
+ </div>
250
  </div>
251
+ <div id="ag3" class="agent-card" style="display:flex;align-items:center;gap:10px;">
252
+ <div class="agent-dot"></div>
253
+ <div style="text-align:left;">
254
+ <div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">Report Builder</div>
255
+ <div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Compiling results</div>
256
+ </div>
257
  </div>
258
  </div>
259
+ </section>
260
 
261
+ <!-- ══ RESULT SECTION ══ -->
262
+ <section id="resultSection" class="hidden fade-up" style="display:flex;flex-direction:column;gap:20px;">
263
 
264
+ <!-- Verdict Card -->
265
+ <div id="verdictCard" class="glass" style="padding:40px;display:flex;flex-wrap:wrap;align-items:center;gap:36px;">
266
+ <!-- Badge + label -->
267
+ <div style="display:flex;flex-direction:column;align-items:center;flex-shrink:0;">
268
+ <div id="verdictBadge" class="verdict-badge" style="margin-bottom:14px;background:rgba(5,5,8,0.8);">
269
+ <span id="verdictEmoji" style="font-size:2.5rem;"></span>
 
 
 
 
 
270
  </div>
271
+ <div id="verdictLabel" style="font-size:28px;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;"></div>
272
+ <div style="font-size:9px;color:var(--muted);letter-spacing:0.25em;margin-top:4px;font-family:'JetBrains Mono',monospace;">VERDICT</div>
273
+ </div>
274
+ <!-- Confidence + risk -->
275
+ <div style="flex:1;min-width:220px;">
276
+ <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px;">
277
+ <span style="font-size:10px;color:var(--muted);letter-spacing:0.15em;text-transform:uppercase;">Confidence Score</span>
278
+ <span id="confValue" style="font-size:36px;font-weight:700;font-family:'JetBrains Mono',monospace;"></span>
279
+ </div>
280
+ <div class="conf-track" style="margin-bottom:20px;">
281
+ <div id="confBar" class="conf-fill" style="width:0%;"></div>
282
+ </div>
283
+ <div class="glass-sm" style="padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
284
+ <span style="font-size:10px;color:var(--muted);letter-spacing:0.15em;text-transform:uppercase;">Risk Assessment</span>
285
+ <span id="riskNeedle"></span>
286
+ </div>
287
+ <div style="margin-top:12px;text-align:right;">
288
+ <span id="riskLabel" style="font-size:11px;color:var(--muted);font-family:'JetBrains Mono',monospace;"></span>
 
 
 
 
 
 
289
  </div>
290
  </div>
291
  </div>
292
 
293
  <!-- Insights + Meta -->
294
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
295
+ <div class="glass" style="padding:24px;">
296
+ <div class="sec-label">Analysis Insights</div>
297
+ <div id="detailsList" style="display:flex;flex-direction:column;gap:10px;"></div>
 
298
  </div>
299
+ <div class="glass" style="padding:24px;display:flex;flex-direction:column;justify-content:space-between;">
300
+ <div>
301
+ <div class="sec-label">Video Metadata</div>
302
+ <div id="metaGrid"></div>
303
+ </div>
304
+ <div style="margin-top:16px;padding-top:14px;border-top:1px solid rgba(255,255,255,0.05);display:flex;align-items:center;gap:8px;">
305
+ <div style="width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:0 0 6px var(--g);"></div>
306
+ <span style="font-size:10px;color:var(--muted);letter-spacing:0.15em;text-transform:uppercase;font-family:'JetBrains Mono',monospace;">Models: ViT Ensemble</span>
 
 
 
 
 
307
  </div>
308
  </div>
309
  </div>
310
 
311
  <!-- Frame Timeline -->
312
+ <div class="glass" style="padding:24px;">
313
+ <div class="sec-label">Frame Analysis Timeline</div>
314
+ <div id="timelineChart" style="display:flex;align-items:flex-end;gap:3px;height:80px;position:relative;padding-bottom:20px;"></div>
315
+ <div style="display:flex;justify-content:space-between;margin-top:8px;">
316
+ <span style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Frame 0</span>
317
+ <span style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Frame 40</span>
 
 
 
 
 
 
 
 
318
  </div>
 
319
  </div>
320
 
321
+ <!-- Reset -->
322
+ <div style="text-align:center;margin-top:8px;">
323
+ <button onclick="resetAll()" style="padding:12px 28px;font-size:11px;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;color:var(--muted);background:none;border:1px solid rgba(255,255,255,0.1);border-radius:10px;cursor:pointer;transition:all 0.3s;font-family:'Space Grotesk',sans-serif;" onmouseover="this.style.color='var(--g)';this.style.borderColor='rgba(0,255,136,0.4)';this.style.background='rgba(0,255,136,0.05)'" onmouseout="this.style.color='var(--muted)';this.style.borderColor='rgba(255,255,255,0.1)';this.style.background='none'">
324
+ ↻ &nbsp;Analyze Another Video
 
 
 
 
325
  </button>
326
  </div>
327
+ </section>
328
 
329
+ <!-- ══ ERROR SECTION ══ -->
330
+ <section id="errorSection" class="hidden glass fade-up" style="padding:28px 32px;margin-bottom:24px;">
331
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;">
332
+ <svg width="22" height="22" fill="none" stroke="#ff3355" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
333
+ <span style="font-size:12px;font-weight:700;letter-spacing:0.2em;color:#ff3355;text-transform:uppercase;font-family:'JetBrains Mono',monospace;">Analysis Failed</span>
 
334
  </div>
335
+ <p id="errorMsg" style="font-size:13px;color:var(--muted);margin-left:34px;line-height:1.6;"></p>
336
+ <div style="margin-left:34px;margin-top:16px;">
337
+ <button onclick="resetAll()" style="font-size:11px;color:rgba(255,255,255,0.4);background:none;border:none;cursor:pointer;letter-spacing:0.15em;text-transform:uppercase;font-family:'Space Grotesk',sans-serif;transition:color 0.2s;" onmouseover="this.style.color='#fff'" onmouseout="this.style.color='rgba(255,255,255,0.4)'">↻ Try Again</button>
338
+ </div>
339
+ </section>
 
 
 
340
 
341
+ </div><!-- /main -->
342
 
343
+ <footer style="position:relative;z-index:1;text-align:center;padding:24px;font-size:10px;color:rgba(90,106,122,0.5);letter-spacing:0.2em;text-transform:uppercase;font-family:'JetBrains Mono',monospace;">
344
+ Deepfake Authenticator &nbsp;Β·&nbsp; Secure Local Inference &nbsp;Β·&nbsp; ViT Ensemble v2
 
345
  </footer>
346
 
347
+ <script>
348
+ // ── Particle background ──────────────────────────────────────────────────────
349
+ (function(){
350
+ const c=document.getElementById('particles');
351
+ const ctx=c.getContext('2d');
352
+ let W,H,pts=[];
353
+ function resize(){W=c.width=window.innerWidth;H=c.height=window.innerHeight;}
354
+ resize();window.addEventListener('resize',resize);
355
+ for(let i=0;i<60;i++)pts.push({x:Math.random()*W,y:Math.random()*H,vx:(Math.random()-0.5)*0.3,vy:(Math.random()-0.5)*0.3,r:Math.random()*1.5+0.5,a:Math.random()});
356
+ function draw(){
357
+ ctx.clearRect(0,0,W,H);
358
+ pts.forEach(p=>{
359
+ p.x+=p.vx;p.y+=p.vy;
360
+ if(p.x<0||p.x>W)p.vx*=-1;
361
+ if(p.y<0||p.y>H)p.vy*=-1;
362
+ ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);
363
+ ctx.fillStyle=`rgba(0,255,136,${0.15+p.a*0.2})`;ctx.fill();
364
+ });
365
+ // draw connections
366
+ for(let i=0;i<pts.length;i++)for(let j=i+1;j<pts.length;j++){
367
+ const dx=pts[i].x-pts[j].x,dy=pts[i].y-pts[j].y,d=Math.sqrt(dx*dx+dy*dy);
368
+ if(d<120){ctx.beginPath();ctx.moveTo(pts[i].x,pts[i].y);ctx.lineTo(pts[j].x,pts[j].y);ctx.strokeStyle=`rgba(0,255,136,${0.06*(1-d/120)})`;ctx.lineWidth=0.5;ctx.stroke();}
369
+ }
370
+ requestAnimationFrame(draw);
371
  }
372
+ draw();
373
+ })();
374
+ </script>
 
 
 
375
  <script src="script.js"></script>
376
  </body>
377
+ </html>
frontend/script.js CHANGED
@@ -1,5 +1,5 @@
1
  /**
2
- * Deepfake Authenticator β€” Frontend Logic
3
  */
4
 
5
  const API_BASE = (window.location.protocol === 'file:')
@@ -11,48 +11,38 @@ let selectedFile = null;
11
  // ── Boot ──────────────────────────────────────
12
  window.addEventListener('load', () => {
13
  initUpload();
14
- pingHealth();
15
  });
16
 
17
- // ── Health ────────────────────────────────────
18
- async function pingHealth() {
19
- const badge = document.getElementById('modelBadge');
20
- for (let i = 0; i < 8; i++) {
21
- try {
22
- const r = await fetch(`${API_BASE}/health`);
23
- if (r.ok) {
24
- const d = await r.json();
25
- if (d.ready) {
26
- badge.textContent = d.model.toUpperCase();
27
- badge.classList.add('ready');
28
- return;
29
- }
30
- }
31
- } catch (_) {}
32
- badge.textContent = `CONNECTING ${i + 1}/8...`;
33
- await sleep(2000);
34
- }
35
- badge.textContent = 'SERVER OFFLINE';
36
- badge.style.borderColor = '#ff444444';
37
- badge.style.color = '#ff4444aa';
38
- }
39
-
40
  // ── Upload wiring ─────────────────────────────
41
  function initUpload() {
42
  const zone = document.getElementById('dropZone');
43
  const input = document.getElementById('fileInput');
44
  const clear = document.getElementById('clearBtn');
 
45
 
46
  zone.addEventListener('click', e => {
47
  if (!e.target.closest('#clearBtn')) input.click();
48
  });
 
49
  input.addEventListener('change', () => {
50
  if (input.files?.[0]) applyFile(input.files[0]);
51
  });
52
- clear.addEventListener('click', e => { e.stopPropagation(); clearFile(); });
53
 
54
- zone.addEventListener('dragover', e => { e.preventDefault(); e.stopPropagation(); zone.classList.add('drag-over'); });
55
- zone.addEventListener('dragleave', e => { e.preventDefault(); zone.classList.remove('drag-over'); });
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  zone.addEventListener('drop', e => {
57
  e.preventDefault(); e.stopPropagation();
58
  zone.classList.remove('drag-over');
@@ -60,6 +50,8 @@ function initUpload() {
60
  if (f?.type.startsWith('video/')) applyFile(f);
61
  else showError('Please drop a valid video file (MP4, AVI, MOV, MKV, WebM).');
62
  });
 
 
63
  }
64
 
65
  function applyFile(file) {
@@ -67,10 +59,8 @@ function applyFile(file) {
67
  document.getElementById('uploadPrompt').classList.add('hidden');
68
  const fc = document.getElementById('fileChosen');
69
  fc.classList.remove('hidden');
70
- fc.style.display = 'flex';
71
  document.getElementById('chosenName').textContent = file.name;
72
  document.getElementById('chosenSize').textContent = fmtBytes(file.size);
73
-
74
  const btn = document.getElementById('analyzeBtn');
75
  btn.disabled = false;
76
  btn.classList.add('active');
@@ -88,16 +78,17 @@ function clearFile() {
88
 
89
  function resetAll() {
90
  clearFile();
91
- ['loadingSection','resultSection','errorSection'].forEach(hide);
 
92
  }
93
 
94
  // ── Analyze ───────────────────────────────────
95
  async function analyzeVideo() {
96
  if (!selectedFile) return;
97
 
 
98
  show('loadingSection');
99
- ['resultSection','errorSection'].forEach(hide);
100
- document.getElementById('analyzeBtn').disabled = true;
101
 
102
  startAgentAnim();
103
 
@@ -110,102 +101,139 @@ async function analyzeVideo() {
110
  const e = await res.json().catch(() => ({}));
111
  throw new Error(e.detail || `Server error ${res.status}`);
112
  }
113
- renderResult(await res.json());
 
114
  } catch (err) {
115
- showError(err.message || 'Unknown error. Is the server running at ' + API_BASE + '?');
116
  } finally {
117
  hide('loadingSection');
118
  stopAgentAnim();
119
- document.getElementById('analyzeBtn').disabled = false;
120
  }
121
  }
122
 
123
- // ── Agent animation ───────────────────────────
124
- const STEPS = [
125
- { id:'ag0', label:'Extracting frames...', prog:12 },
126
- { id:'ag1', label:'Detecting faces...', prog:38 },
127
- { id:'ag2', label:'Running AI ensemble...', prog:78 },
128
- { id:'ag3', label:'Generating report...', prog:94 },
129
- ];
130
- const _timers = [];
131
 
132
  function startAgentAnim() {
133
- const delays = [0, 1600, 4200, 7500];
134
- STEPS.forEach((s, i) => {
135
- _timers.push(setTimeout(() => {
136
- const c = document.getElementById(s.id);
137
- if (!c) return;
138
- c.classList.add('active');
139
- c.querySelector('.a-status').textContent = s.label;
140
- document.getElementById('progressBar').style.width = s.prog + '%';
141
- document.getElementById('loadingStatus').textContent = s.label.toUpperCase();
142
- }, delays[i]));
 
 
 
 
 
143
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  }
145
 
146
  function stopAgentAnim() {
147
- _timers.forEach(clearTimeout); _timers.length = 0;
148
- STEPS.forEach(s => {
149
- const c = document.getElementById(s.id);
150
- if (!c) return;
151
- c.classList.remove('active');
152
- c.querySelector('.a-status').textContent = 'Waiting...';
153
  });
154
- document.getElementById('progressBar').style.width = '0%';
155
  }
156
 
157
- // ── Render result ─────────────────────────────
158
  function renderResult(data) {
159
- const fake = data.result === 'FAKE';
160
- const pct = data.confidence;
161
 
162
- // Verdict card
163
  const vc = document.getElementById('verdictCard');
164
- vc.className = 'fade-up ' + (fake ? 'fake' : 'real');
 
 
 
165
 
166
  // Badge
167
  const badge = document.getElementById('verdictBadge');
168
- badge.className = 'verdict-badge ' + (fake ? 'fake' : 'real');
169
- document.getElementById('verdictEmoji').textContent = fake ? '⚠️' : 'βœ…';
 
 
 
170
 
171
  // Label
172
  const lbl = document.getElementById('verdictLabel');
173
- lbl.textContent = data.result;
174
- lbl.style.color = fake ? 'var(--red)' : 'var(--neon)';
175
- lbl.style.textShadow = fake ? '0 0 20px #ff444466' : '0 0 20px #00ff8866';
176
-
177
- // Confidence
178
- document.getElementById('confValue').textContent = pct + '%';
179
- document.getElementById('confValue').style.color = fake ? 'var(--red)' : 'var(--neon)';
 
 
 
 
 
 
 
180
  const bar = document.getElementById('confBar');
181
- bar.className = 'conf-fill ' + (fake ? 'fake' : 'real');
182
  setTimeout(() => { bar.style.width = pct + '%'; }, 80);
183
 
184
  // Risk needle
185
  const needle = document.getElementById('riskNeedle');
186
  const riskLbl = document.getElementById('riskLabel');
187
- setTimeout(() => { needle.style.left = pct + '%'; }, 80);
188
  if (pct < 35) {
189
- riskLbl.textContent = 'LOW'; riskLbl.style.color = 'var(--neon)';
190
- } else if (pct < 60) {
191
- riskLbl.textContent = 'MEDIUM'; riskLbl.style.color = '#facc15';
192
- } else if (pct < 80) {
193
- riskLbl.textContent = 'HIGH'; riskLbl.style.color = '#f97316';
 
 
 
 
 
 
194
  } else {
195
- riskLbl.textContent = 'CRITICAL'; riskLbl.style.color = 'var(--red)';
 
 
 
 
196
  }
197
 
198
  // Insights
199
  const dl = document.getElementById('detailsList');
200
  dl.innerHTML = '';
201
- (data.details || []).forEach((txt, i) => {
 
 
 
202
  const div = document.createElement('div');
203
  div.className = 'insight-item';
204
- div.style.animationDelay = (i * 60) + 'ms';
205
- div.innerHTML =
206
- `<span class="dot" style="background:${fake ? 'var(--red)' : 'var(--neon)'};
207
- box-shadow:0 0 6px ${fake ? '#ff444466' : '#00ff8866'};"></span>
208
- <span>${esc(txt)}</span>`;
209
  dl.appendChild(div);
210
  });
211
 
@@ -213,89 +241,114 @@ function renderResult(data) {
213
  const meta = data.metadata || {};
214
  const mg = document.getElementById('metaGrid');
215
  mg.innerHTML = '';
216
- [
217
- ['Frames Analyzed', meta.frames_analyzed ?? 'β€”'],
218
- ['Faces Detected', meta.frames_with_faces ?? 'β€”'],
219
- ['Duration', meta.video_duration_sec ? meta.video_duration_sec + 's' : 'β€”'],
220
- ['FPS', meta.video_fps ?? 'β€”'],
221
- ['Resolution', meta.resolution ?? 'β€”'],
222
- ['Processing Time', data.processing_time_sec ? data.processing_time_sec + 's' : 'β€”'],
223
- ].forEach(([k, v]) => {
224
  const row = document.createElement('div');
225
  row.className = 'meta-row';
226
- row.innerHTML = `<span class="meta-key">${k}</span><span class="meta-val">${v}</span>`;
227
  mg.appendChild(row);
228
  });
229
 
230
- // Timeline
231
- buildTimeline(data.frame_timeline || []);
232
 
233
  show('resultSection');
234
- document.getElementById('resultSection').scrollIntoView({ behavior: 'smooth', block: 'start' });
235
  }
236
 
237
- // ── Timeline ──────────────────────────────────
238
- function buildTimeline(frames) {
239
  const chart = document.getElementById('timelineChart');
 
240
  chart.innerHTML = '';
241
 
 
242
  if (!frames.length) {
243
- chart.innerHTML = '<p style="font-size:11px;color:var(--muted);">No timeline data available</p>';
244
  return;
245
  }
246
 
247
- const maxH = 72;
248
- const threshold = 60;
 
249
 
250
- // Threshold line
251
- const line = document.createElement('div');
252
- line.className = 'threshold-line';
253
- line.style.bottom = ((threshold / 100) * maxH) + 'px';
254
- chart.appendChild(line);
 
 
 
 
 
 
255
 
256
- frames.forEach((pt, idx) => {
257
- const pct = pt.fake_pct;
258
- const h = Math.max(4, (pct / 100) * maxH);
259
- const hot = pct >= threshold;
260
-
261
- const bar = document.createElement('div');
262
- bar.className = 't-bar';
263
- bar.style.height = h + 'px';
264
- bar.style.background = hot
265
- ? 'linear-gradient(to top, #ff2222, #ff6666)'
266
- : 'linear-gradient(to top, #00cc6a, #00ff88)';
267
- bar.style.boxShadow = hot ? '0 0 6px #ff444455' : '0 0 4px #00ff8844';
268
- bar.style.opacity = '0';
269
- bar.style.transition = 'opacity .3s ease, transform .2s ease';
270
-
271
- // Tooltip
272
- bar.innerHTML = `<div class="tooltip">Frame ${pt.frame}<br>${pct}% fake</div>`;
273
-
274
- chart.appendChild(bar);
275
- setTimeout(() => { bar.style.opacity = '1'; }, 30 + idx * 20);
276
  });
277
- }
278
 
279
- // ── Error ─────────────────────────────────────
280
- function showError(msg) {
281
- hide('loadingSection');
282
- document.getElementById('errorMsg').textContent = msg;
283
- show('errorSection');
 
 
 
284
  }
285
 
286
  // ── Helpers ───────────────────────────────────
287
  function show(id) {
288
  const el = document.getElementById(id);
 
289
  el.classList.remove('hidden');
290
- el.style.display = '';
291
  }
292
- function hide(id) { document.getElementById(id).classList.add('hidden'); }
293
- function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
 
 
 
 
294
  function fmtBytes(b) {
295
- if (b < 1024) return b + ' B';
296
- if (b < 1048576) return (b/1024).toFixed(1) + ' KB';
297
- return (b/1048576).toFixed(1) + ' MB';
298
  }
 
299
  function esc(s) {
300
- return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
 
 
 
 
 
 
 
 
 
 
301
  }
 
1
  /**
2
+ * Deepfake Authenticator β€” Frontend Logic (Cinematic Dark UI)
3
  */
4
 
5
  const API_BASE = (window.location.protocol === 'file:')
 
11
  // ── Boot ──────────────────────────────────────
12
  window.addEventListener('load', () => {
13
  initUpload();
 
14
  });
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  // ── Upload wiring ─────────────────────────────
17
  function initUpload() {
18
  const zone = document.getElementById('dropZone');
19
  const input = document.getElementById('fileInput');
20
  const clear = document.getElementById('clearBtn');
21
+ const btn = document.getElementById('analyzeBtn');
22
 
23
  zone.addEventListener('click', e => {
24
  if (!e.target.closest('#clearBtn')) input.click();
25
  });
26
+
27
  input.addEventListener('change', () => {
28
  if (input.files?.[0]) applyFile(input.files[0]);
29
  });
 
30
 
31
+ clear.addEventListener('click', e => {
32
+ e.stopPropagation();
33
+ clearFile();
34
+ });
35
+
36
+ zone.addEventListener('dragover', e => {
37
+ e.preventDefault(); e.stopPropagation();
38
+ zone.classList.add('drag-over');
39
+ });
40
+
41
+ zone.addEventListener('dragleave', e => {
42
+ e.preventDefault();
43
+ zone.classList.remove('drag-over');
44
+ });
45
+
46
  zone.addEventListener('drop', e => {
47
  e.preventDefault(); e.stopPropagation();
48
  zone.classList.remove('drag-over');
 
50
  if (f?.type.startsWith('video/')) applyFile(f);
51
  else showError('Please drop a valid video file (MP4, AVI, MOV, MKV, WebM).');
52
  });
53
+
54
+ btn.addEventListener('click', analyzeVideo);
55
  }
56
 
57
  function applyFile(file) {
 
59
  document.getElementById('uploadPrompt').classList.add('hidden');
60
  const fc = document.getElementById('fileChosen');
61
  fc.classList.remove('hidden');
 
62
  document.getElementById('chosenName').textContent = file.name;
63
  document.getElementById('chosenSize').textContent = fmtBytes(file.size);
 
64
  const btn = document.getElementById('analyzeBtn');
65
  btn.disabled = false;
66
  btn.classList.add('active');
 
78
 
79
  function resetAll() {
80
  clearFile();
81
+ ['loadingSection', 'resultSection', 'errorSection'].forEach(hide);
82
+ show('uploadSection');
83
  }
84
 
85
  // ── Analyze ───────────────────────────────────
86
  async function analyzeVideo() {
87
  if (!selectedFile) return;
88
 
89
+ hide('uploadSection');
90
  show('loadingSection');
91
+ ['resultSection', 'errorSection'].forEach(hide);
 
92
 
93
  startAgentAnim();
94
 
 
101
  const e = await res.json().catch(() => ({}));
102
  throw new Error(e.detail || `Server error ${res.status}`);
103
  }
104
+ const data = await res.json();
105
+ renderResult(data);
106
  } catch (err) {
107
+ showError(err.message || 'Connection to analysis engine failed.');
108
  } finally {
109
  hide('loadingSection');
110
  stopAgentAnim();
 
111
  }
112
  }
113
 
114
+ // ── Loading Animation ─────────────────────────
115
+ let _simTimer = null;
116
+ let _agentTimer = null;
 
 
 
 
 
117
 
118
  function startAgentAnim() {
119
+ const statusEl = document.getElementById('loadingStatus');
120
+ const progBar = document.getElementById('progressBar');
121
+
122
+ const phases = [
123
+ { p: 12, msg: 'Extracting keyframes...', ag: 0 },
124
+ { p: 35, msg: 'Isolating facial regions...', ag: 1 },
125
+ { p: 65, msg: 'Running ViT neural inference...', ag: 2 },
126
+ { p: 85, msg: 'Cross-referencing metadata...', ag: 2 },
127
+ { p: 95, msg: 'Compiling authenticity report...', ag: 3 },
128
+ ];
129
+
130
+ // Reset agents
131
+ [0,1,2,3].forEach(i => {
132
+ const card = document.getElementById('ag' + i);
133
+ if (card) card.classList.remove('active');
134
  });
135
+
136
+ statusEl.textContent = 'Initializing sequence...';
137
+ progBar.style.width = '0%';
138
+
139
+ let idx = 0;
140
+ _simTimer = setInterval(() => {
141
+ if (idx < phases.length) {
142
+ const ph = phases[idx];
143
+ statusEl.textContent = ph.msg;
144
+ progBar.style.width = ph.p + '%';
145
+ const card = document.getElementById('ag' + ph.ag);
146
+ if (card) card.classList.add('active');
147
+ idx++;
148
+ }
149
+ }, 1100);
150
  }
151
 
152
  function stopAgentAnim() {
153
+ if (_simTimer) { clearInterval(_simTimer); _simTimer = null; }
154
+ document.getElementById('progressBar').style.width = '100%';
155
+ [0,1,2,3].forEach(i => {
156
+ const card = document.getElementById('ag' + i);
157
+ if (card) card.classList.add('active');
 
158
  });
 
159
  }
160
 
161
+ // ── Render Result ─────────────────────────────
162
  function renderResult(data) {
163
+ const isFake = data.result === 'FAKE';
164
+ const pct = data.confidence;
165
 
166
+ // Verdict card border glow
167
  const vc = document.getElementById('verdictCard');
168
+ vc.style.borderColor = isFake ? 'rgba(255,51,85,0.5)' : 'rgba(0,255,136,0.5)';
169
+ vc.style.boxShadow = isFake
170
+ ? '0 0 50px rgba(255,51,85,0.15), inset 0 0 30px rgba(255,51,85,0.05)'
171
+ : '0 0 50px rgba(0,255,136,0.15), inset 0 0 30px rgba(0,255,136,0.05)';
172
 
173
  // Badge
174
  const badge = document.getElementById('verdictBadge');
175
+ badge.className = 'verdict-badge ' + (isFake ? 'fake' : 'real');
176
+ badge.style.background = isFake ? 'rgba(255,51,85,0.08)' : 'rgba(0,255,136,0.08)';
177
+
178
+ // Emoji
179
+ document.getElementById('verdictEmoji').textContent = isFake ? '⚠' : 'βœ“';
180
 
181
  // Label
182
  const lbl = document.getElementById('verdictLabel');
183
+ lbl.textContent = isFake ? 'DEEPFAKE' : 'AUTHENTIC';
184
+ lbl.style.color = isFake ? '#ff3355' : '#00ff88';
185
+ lbl.style.textShadow = isFake
186
+ ? '0 0 20px rgba(255,51,85,0.7)'
187
+ : '0 0 20px rgba(0,255,136,0.7)';
188
+ if (isFake) lbl.classList.add('glitch-text');
189
+ else lbl.classList.remove('glitch-text');
190
+
191
+ // Confidence value
192
+ const cv = document.getElementById('confValue');
193
+ cv.textContent = pct + '%';
194
+ cv.style.color = isFake ? '#ff3355' : '#00ff88';
195
+
196
+ // Confidence bar
197
  const bar = document.getElementById('confBar');
198
+ bar.className = 'conf-fill ' + (isFake ? 'fake' : 'real');
199
  setTimeout(() => { bar.style.width = pct + '%'; }, 80);
200
 
201
  // Risk needle
202
  const needle = document.getElementById('riskNeedle');
203
  const riskLbl = document.getElementById('riskLabel');
 
204
  if (pct < 35) {
205
+ needle.textContent = 'LOW RISK';
206
+ needle.style.color = '#00ff88';
207
+ needle.style.borderColor = 'rgba(0,255,136,0.35)';
208
+ needle.style.background = 'rgba(0,255,136,0.08)';
209
+ if (riskLbl) riskLbl.textContent = 'Minimal manipulation indicators detected';
210
+ } else if (pct < 65) {
211
+ needle.textContent = 'MEDIUM RISK';
212
+ needle.style.color = '#ffaa00';
213
+ needle.style.borderColor = 'rgba(255,170,0,0.35)';
214
+ needle.style.background = 'rgba(255,170,0,0.08)';
215
+ if (riskLbl) riskLbl.textContent = 'Moderate anomalies detected β€” review advised';
216
  } else {
217
+ needle.textContent = 'CRITICAL RISK';
218
+ needle.style.color = '#ff3355';
219
+ needle.style.borderColor = 'rgba(255,51,85,0.35)';
220
+ needle.style.background = 'rgba(255,51,85,0.08)';
221
+ if (riskLbl) riskLbl.textContent = 'High-confidence manipulation signatures found';
222
  }
223
 
224
  // Insights
225
  const dl = document.getElementById('detailsList');
226
  dl.innerHTML = '';
227
+ const details = data.details || ['Analysis completed successfully.'];
228
+ const dotColor = isFake ? '#ff3355' : '#00ff88';
229
+ const dotGlow = isFake ? 'rgba(255,51,85,0.6)' : 'rgba(0,255,136,0.6)';
230
+ details.forEach((txt, i) => {
231
  const div = document.createElement('div');
232
  div.className = 'insight-item';
233
+ div.style.animationDelay = (i * 0.08) + 's';
234
+ div.style.borderLeftColor = dotColor;
235
+ div.style.borderLeft = `2px solid ${dotColor}`;
236
+ div.innerHTML = `<span class="insight-dot" style="background:${dotColor};box-shadow:0 0 8px ${dotGlow};"></span><span>${esc(txt)}</span>`;
 
237
  dl.appendChild(div);
238
  });
239
 
 
241
  const meta = data.metadata || {};
242
  const mg = document.getElementById('metaGrid');
243
  mg.innerHTML = '';
244
+ const metaItems = [
245
+ ['Frames Analyzed', meta.frames_analyzed ?? 'β€”'],
246
+ ['Duration', meta.video_duration_sec ? meta.video_duration_sec + 's' : 'β€”'],
247
+ ['FPS', meta.video_fps ?? 'β€”'],
248
+ ['Resolution', meta.resolution ?? 'β€”'],
249
+ ['Processing Time', data.processing_time_sec ? data.processing_time_sec + 's' : 'β€”'],
250
+ ];
251
+ metaItems.forEach(([k, v]) => {
252
  const row = document.createElement('div');
253
  row.className = 'meta-row';
254
+ row.innerHTML = `<span style="font-size:11px;color:var(--muted);letter-spacing:0.08em;">${k}</span><span style="font-size:13px;font-weight:600;color:#fff;font-family:'JetBrains Mono',monospace;">${v}</span>`;
255
  mg.appendChild(row);
256
  });
257
 
258
+ // Frame timeline
259
+ renderTimeline(data, isFake);
260
 
261
  show('resultSection');
 
262
  }
263
 
264
+ function renderTimeline(data, isFake) {
 
265
  const chart = document.getElementById('timelineChart');
266
+ if (!chart) return;
267
  chart.innerHTML = '';
268
 
269
+ const frames = data.frame_scores || [];
270
  if (!frames.length) {
271
+ chart.innerHTML = '<span style="font-size:11px;color:var(--muted);font-family:\'JetBrains Mono\',monospace;margin:auto;">No per-frame data available</span>';
272
  return;
273
  }
274
 
275
+ const maxH = 60; // px
276
+ const barColor = isFake ? '#ff3355' : '#00ff88';
277
+ const barGlow = isFake ? 'rgba(255,51,85,0.5)' : 'rgba(0,255,136,0.5)';
278
 
279
+ frames.forEach((score, i) => {
280
+ const pct = Math.round(score * 100);
281
+ const h = Math.max(4, Math.round((score) * maxH));
282
+
283
+ const wrap = document.createElement('div');
284
+ wrap.className = 'bar-wrap';
285
+ wrap.style.height = maxH + 'px';
286
+
287
+ const outer = document.createElement('div');
288
+ outer.className = 'bar-outer';
289
+ outer.style.height = maxH + 'px';
290
 
291
+ const inner = document.createElement('div');
292
+ inner.className = 'bar-inner';
293
+ inner.style.height = '0px';
294
+ inner.style.background = score > 0.5
295
+ ? `linear-gradient(to top, ${barColor}, rgba(255,255,255,0.3))`
296
+ : 'rgba(255,255,255,0.12)';
297
+ if (score > 0.5) inner.style.boxShadow = `0 0 8px ${barGlow}`;
298
+
299
+ outer.appendChild(inner);
300
+
301
+ const tip = document.createElement('div');
302
+ tip.className = 'bar-tooltip';
303
+ tip.textContent = `F${i+1}: ${pct}%`;
304
+
305
+ wrap.appendChild(outer);
306
+ wrap.appendChild(tip);
307
+ chart.appendChild(wrap);
308
+
309
+ // Animate in
310
+ setTimeout(() => { inner.style.height = h + 'px'; }, 50 + i * 20);
311
  });
 
312
 
313
+ // Threshold line at 50%
314
+ const line = document.createElement('div');
315
+ line.style.cssText = `position:absolute;left:0;right:0;bottom:${maxH*0.5}px;height:1px;background:rgba(255,170,0,0.4);pointer-events:none;`;
316
+ const lineLbl = document.createElement('span');
317
+ lineLbl.style.cssText = 'position:absolute;right:4px;top:-14px;font-size:9px;color:#ffaa00;font-family:\'JetBrains Mono\',monospace;letter-spacing:0.1em;';
318
+ lineLbl.textContent = '50%';
319
+ line.appendChild(lineLbl);
320
+ chart.appendChild(line);
321
  }
322
 
323
  // ── Helpers ───────────────────────────────────
324
  function show(id) {
325
  const el = document.getElementById(id);
326
+ if (!el) return;
327
  el.classList.remove('hidden');
328
+ if (el.style.display === 'none') el.style.display = '';
329
  }
330
+
331
+ function hide(id) {
332
+ const el = document.getElementById(id);
333
+ if (el) el.classList.add('hidden');
334
+ }
335
+
336
  function fmtBytes(b) {
337
+ if (b < 1024) return b + ' B';
338
+ if (b < 1048576) return (b / 1024).toFixed(1) + ' KB';
339
+ return (b / 1048576).toFixed(1) + ' MB';
340
  }
341
+
342
  function esc(s) {
343
+ return String(s)
344
+ .replace(/&/g, '&amp;')
345
+ .replace(/</g, '&lt;')
346
+ .replace(/>/g, '&gt;');
347
+ }
348
+
349
+ function showError(msg) {
350
+ hide('uploadSection');
351
+ hide('loadingSection');
352
+ document.getElementById('errorMsg').textContent = msg;
353
+ show('errorSection');
354
  }