Deepfake Authenticator commited on
Commit
faea1f9
Β·
1 Parent(s): 459d5a1

feat: integrate Authrix Kinetic UI design system

Browse files

- Full Authrix design system: Space Grotesk, #0c150f bg, #00ff9c neon green
- 4-state SPA: Hero β†’ Upload β†’ Processing β†’ Results
- Hero: spinning 3-ring orb, gradient headline, stat pills, floating HUD
- Upload: glassmorphic drop zone with scan-line, drag/drop, live queue clock
- Processing: rotating orb, 4 sequential pipeline steps with segmented bars, CLI terminal
- Results: cinematic banner with circular verdict badge, 80px confidence score,
Primary Vectors insight cards, System Diagnostics terminal, Probability Timeline
with actual frame_timeline data, Audio analysis section
- All wired to FastAPI /analyze and /health endpoints

Files changed (1) hide show
  1. frontend-vanilla/index.html +1226 -317
frontend-vanilla/index.html CHANGED
@@ -1,377 +1,1286 @@
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>
 
1
  ο»Ώ<!DOCTYPE html>
2
+ <html class="dark" lang="en">
3
  <head>
4
+ <meta charset="utf-8"/>
5
+ <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
6
+ <title>AUTHRIX AI - Deepfake Detection Engine</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link href="https://fonts.googleapis.com" rel="preconnect"/>
10
+ <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
11
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;900&display=swap" rel="stylesheet"/>
12
+ <!-- Material Symbols -->
13
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
14
+ <!-- Tailwind CSS -->
15
+ <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
16
+ <script>
17
+ tailwind.config = {
18
+ darkMode: "class",
19
+ theme: {
20
+ extend: {
21
+ colors: {
22
+ "inverse-surface": "#dae5da",
23
+ "on-error": "#690005",
24
+ "surface-tint": "#00e38a",
25
+ "error": "#ffb4ab",
26
+ "outline-variant": "#3b4b3f",
27
+ "outline": "#849587",
28
+ "background": "#0c150f",
29
+ "surface-container-high": "#232c25",
30
+ "secondary-container": "#00e3fd",
31
+ "on-surface-variant": "#b9cbbc",
32
+ "on-secondary-container": "#00616d",
33
+ "on-error-container": "#ffdad6",
34
+ "primary": "#f3fff3",
35
+ "primary-container": "#00ff9c",
36
+ "on-primary": "#00391f",
37
+ "surface-bright": "#323c34",
38
+ "primary-fixed-dim": "#00e38a",
39
+ "tertiary-fixed-dim": "#e4c44f",
40
+ "on-secondary-fixed": "#001f24",
41
+ "tertiary-fixed": "#ffe17a",
42
+ "on-tertiary-container": "#766000",
43
+ "on-secondary": "#00363d",
44
+ "on-tertiary-fixed": "#231b00",
45
+ "on-tertiary": "#3b2f00",
46
+ "surface-container-low": "#141e17",
47
+ "surface": "#0c150f",
48
+ "primary-fixed": "#56ffa7",
49
+ "secondary-fixed-dim": "#00daf3",
50
+ "on-secondary-fixed-variant": "#004f58",
51
+ "inverse-on-surface": "#29332b",
52
+ "surface-container": "#18221b",
53
+ "on-surface": "#dae5da",
54
+ "on-tertiary-fixed-variant": "#554500",
55
+ "error-container": "#93000a",
56
+ "surface-variant": "#2d3730",
57
+ "surface-container-highest": "#2d3730",
58
+ "secondary-fixed": "#9cf0ff",
59
+ "on-background": "#dae5da",
60
+ "on-primary-container": "#007142",
61
+ "inverse-primary": "#006d40",
62
+ "on-primary-fixed-variant": "#00522f",
63
+ "secondary": "#bdf4ff",
64
+ "tertiary": "#fffaff",
65
+ "surface-dim": "#0c150f",
66
+ "on-primary-fixed": "#002110",
67
+ "surface-container-lowest": "#07100a",
68
+ "tertiary-container": "#ffdd65"
69
+ },
70
+ borderRadius: {
71
+ "DEFAULT": "0.25rem",
72
+ "lg": "0.5rem",
73
+ "xl": "0.75rem",
74
+ "full": "9999px"
75
+ },
76
+ spacing: {
77
+ "xs": "4px",
78
+ "md": "16px",
79
+ "base": "4px",
80
+ "sm": "8px",
81
+ "xl": "48px",
82
+ "lg": "24px",
83
+ "gutter": "20px",
84
+ "margin": "32px"
85
+ },
86
+ fontFamily: {
87
+ "h2": ["Space Grotesk"],
88
+ "h1": ["Space Grotesk"],
89
+ "h3": ["Space Grotesk"],
90
+ "label-xs": ["Space Grotesk"],
91
+ "data-mono": ["Space Grotesk"],
92
+ "body-base": ["Space Grotesk"]
93
+ },
94
+ fontSize: {
95
+ "h2": ["32px", { lineHeight: "1.2", letterSpacing: "-0.01em", fontWeight: "600" }],
96
+ "h1": ["48px", { lineHeight: "1.1", letterSpacing: "-0.02em", fontWeight: "700" }],
97
+ "h3": ["24px", { lineHeight: "1.3", letterSpacing: "0em", fontWeight: "600" }],
98
+ "label-xs": ["12px", { lineHeight: "1", letterSpacing: "0.1em", fontWeight: "700" }],
99
+ "data-mono": ["14px", { lineHeight: "1.5", letterSpacing: "0.05em", fontWeight: "500" }],
100
+ "body-base": ["16px", { lineHeight: "1.6", letterSpacing: "0.01em", fontWeight: "400" }]
101
+ }
102
+ }
103
+ }
104
+ };
105
+ </script>
106
+
107
  <style>
108
+ /* ── Material Symbols ── */
109
+ .material-symbols-outlined {
110
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
111
+ }
112
+
113
+ /* ── Scan line animation ── */
114
+ .scan-line {
115
+ background: linear-gradient(to bottom, transparent 0%, rgba(0, 255, 156, 0.15) 50%, transparent 100%);
116
+ animation: scanMove 4s linear infinite;
117
+ height: 2px;
118
+ width: 100%;
119
+ position: absolute;
120
+ top: 0;
121
+ left: 0;
122
+ pointer-events: none;
123
+ opacity: 0.6;
124
+ }
125
+ @keyframes scanMove {
126
+ 0% { top: 0%; }
127
+ 100% { top: 100%; }
128
+ }
129
+
130
+ /* ── Scanline overlay (results banner) ── */
131
+ .scanline {
132
+ position: absolute;
133
+ top: 0; left: 0;
134
+ width: 100%; height: 1px;
135
+ background: rgba(0, 255, 156, 0.12);
136
+ animation: scanMove 4s linear infinite;
137
+ }
138
+
139
+ /* ── Noise background ── */
140
+ .noise-bg {
141
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
142
+ opacity: 0.05;
143
+ mix-blend-mode: overlay;
144
+ pointer-events: none;
145
+ }
146
+
147
+ /* ── Glass panel ── */
148
+ .glass-panel {
149
+ background: rgba(35, 44, 37, 0.2);
150
+ backdrop-filter: blur(20px);
151
+ border: 1px solid rgba(0, 255, 156, 0.1);
152
+ box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.05);
153
+ }
154
+
155
+ /* ── Glow borders ── */
156
+ .glow-border-primary {
157
+ border: 1px solid rgba(0, 255, 156, 0.3) !important;
158
+ box-shadow: 0 0 15px rgba(0, 255, 156, 0.1);
159
+ }
160
+ .glow-border-error {
161
+ border: 1px solid rgba(255, 180, 171, 0.3) !important;
162
+ box-shadow: 0 0 15px rgba(255, 180, 171, 0.1);
163
+ }
164
+ .glow-border-active {
165
+ border: 1px solid rgba(0, 255, 156, 0.5);
166
+ box-shadow: 0 0 15px rgba(0, 255, 156, 0.2);
167
+ }
168
+
169
+ /* ── Blinking cursor ── */
170
+ .blinking-cursor::after {
171
+ content: '_';
172
+ animation: blink 1s step-end infinite;
173
+ }
174
+ @keyframes blink {
175
+ 0%, 100% { opacity: 1; }
176
+ 50% { opacity: 0; }
177
+ }
178
+
179
+ /* ── Shimmer on hero CTA ── */
180
+ @keyframes shimmer {
181
+ 100% { transform: translateX(200%); }
182
+ }
183
+
184
+ /* ── Drag-over state ── */
185
+ #dropZoneInner.drag-over {
186
+ border-color: #00ff9c !important;
187
+ background: rgba(0, 255, 156, 0.05) !important;
188
+ box-shadow: 0 0 40px rgba(0, 255, 156, 0.15) inset !important;
189
+ }
190
+
191
+ /* ── Pipeline step active ── */
192
+ .pipeline-step { transition: opacity 0.4s ease; }
193
+ .pipeline-step.pending { opacity: 0.3; }
194
+ .pipeline-step.active { opacity: 1; }
195
+ .pipeline-step.done { opacity: 0.6; }
196
+
197
+ /* ── Timeline bar ── */
198
+ .tl-bar {
199
+ flex: 1;
200
+ min-width: 0;
201
+ display: flex;
202
+ flex-direction: column;
203
+ align-items: stretch;
204
+ position: relative;
205
+ }
206
+ .tl-bar-outer {
207
+ width: 100%;
208
+ height: 100%;
209
+ background: rgba(255,255,255,0.05);
210
+ border-radius: 2px 2px 0 0;
211
+ overflow: hidden;
212
+ position: relative;
213
+ flex: 1;
214
+ }
215
+ .tl-bar-inner {
216
+ width: 100%;
217
+ border-radius: 2px 2px 0 0;
218
+ transition: height 0.8s cubic-bezier(0.22,1,0.36,1);
219
+ }
220
+ .tl-bar:hover .tl-tooltip {
221
+ opacity: 1;
222
+ transform: translateX(-50%) translateY(-4px);
223
+ }
224
+ .tl-tooltip {
225
+ position: absolute;
226
+ bottom: calc(100% + 6px);
227
+ left: 50%;
228
+ transform: translateX(-50%);
229
+ background: rgba(10,10,20,0.95);
230
+ border: 1px solid rgba(255,255,255,0.1);
231
+ border-radius: 4px;
232
+ padding: 3px 7px;
233
+ font-size: 10px;
234
+ white-space: nowrap;
235
+ pointer-events: none;
236
+ opacity: 0;
237
+ transition: all 0.2s ease;
238
+ z-index: 20;
239
+ font-family: 'Space Grotesk', sans-serif;
240
+ color: #dae5da;
241
+ }
242
+
243
+ /* ── Scrollbar ── */
244
+ ::-webkit-scrollbar { width: 5px; }
245
+ ::-webkit-scrollbar-track { background: #0c150f; }
246
+ ::-webkit-scrollbar-thumb { background: rgba(0,255,156,0.25); border-radius: 4px; }
247
  </style>
248
  </head>
 
249
 
250
+ <body class="bg-background text-on-surface min-h-screen flex flex-col relative overflow-x-hidden font-body-base">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ <!-- ═══════════════════════════════════════════════════════════
253
+ FIXED ATMOSPHERIC BACKGROUND (all states)
254
+ ════════════════════════════════════════════════════════════ -->
255
+ <div class="fixed inset-0 z-0 pointer-events-none">
256
+ <!-- Scan lines -->
257
+ <div class="absolute inset-0 bg-[linear-gradient(rgba(0,0,0,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] bg-[size:100%_4px,3px_100%] opacity-20"></div>
258
+ <!-- Technical grid -->
259
+ <div class="absolute inset-0 bg-[linear-gradient(to_right,#3b4b3f_1px,transparent_1px),linear-gradient(to_bottom,#3b4b3f_1px,transparent_1px)] bg-[size:48px_48px] opacity-10"></div>
260
+ <!-- Ambient glow -->
261
+ <div class="absolute top-1/4 -right-1/4 w-[800px] h-[800px] bg-primary-container/5 rounded-full blur-[120px]"></div>
262
+ <div class="absolute bottom-[-20%] left-[-10%] w-[50%] h-[50%] bg-primary-container/3 rounded-full blur-[120px]"></div>
263
+ <!-- Noise -->
264
+ <div class="absolute inset-0 noise-bg"></div>
265
+ </div>
266
+
267
+ <!-- ═════════════════════════════════════════════════���═════════
268
+ FIXED NAV (visible across all states)
269
+ ════════════════════════════════════════════════════════════ -->
270
+ <nav class="fixed top-0 left-0 w-full z-50 flex justify-between items-center px-6 h-16 bg-emerald-950/20 backdrop-blur-xl border-b border-emerald-500/20 shadow-[0_4px_20px_rgba(0,0,0,0.5)] font-['Space_Grotesk'] tracking-wider uppercase text-xs">
271
+ <!-- Brand -->
272
+ <div class="flex items-center gap-8">
273
+ <span class="text-2xl font-black tracking-tighter text-emerald-400 drop-shadow-[0_0_8px_rgba(0,255,156,0.5)]">AUTHRIX AI</span>
274
+ <div class="hidden md:flex items-center gap-6">
275
+ <a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm" href="#">Dashboard</a>
276
+ <a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm" href="#">Agents</a>
277
+ <a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm" href="#">Logs</a>
278
+ <a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm" href="#">Network</a>
279
+ </div>
280
+ </div>
281
+ <!-- Actions -->
282
+ <div class="flex items-center gap-4">
283
+ <!-- Health badge -->
284
+ <div id="healthBadge" class="hidden items-center gap-2 bg-surface-container/40 border border-outline/30 px-3 py-1 rounded-sm">
285
+ <span id="healthDot" class="w-2 h-2 rounded-full bg-outline"></span>
286
+ <span id="healthText" class="font-label-xs text-label-xs text-on-surface-variant">CHECKING</span>
287
+ </div>
288
+ <div class="hidden lg:flex items-center gap-2 border-r border-emerald-500/20 pr-4 mr-2">
289
+ <button class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 p-1 rounded-sm"><span class="material-symbols-outlined text-lg">sensors</span></button>
290
+ <button class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 p-1 rounded-sm"><span class="material-symbols-outlined text-lg">memory</span></button>
291
+ <button class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 p-1 rounded-sm"><span class="material-symbols-outlined text-lg">speed</span></button>
292
+ </div>
293
+ <button onclick="showState('uploadSection')" class="text-emerald-400 border border-emerald-400/50 hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] px-3 py-1 rounded-DEFAULT font-bold transition-all active:scale-95">
294
+ GET STARTED
295
+ </button>
296
+ </div>
297
+ </nav>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
+ <!-- ═══════════════════════════════════════════════════════════
300
+ STATE 1: HERO SECTION
301
+ ════════════════════════════════════════════════════════════ -->
302
+ <section id="heroSection" class="relative z-10 flex flex-col justify-center pt-[100px] pb-xl px-6 md:px-gutter max-w-7xl mx-auto w-full min-h-screen">
303
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-xl items-center min-h-[716px]">
304
 
305
+ <!-- Left: Content -->
306
+ <div class="flex flex-col items-start space-y-md">
307
+ <!-- Status badge -->
308
+ <div class="inline-flex items-center gap-2 bg-surface-container/40 border border-outline/30 backdrop-blur-md px-sm py-xs rounded-none shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]">
309
+ <span class="w-2 h-2 rounded-full bg-primary-container shadow-[0_0_8px_#00ff9c] animate-pulse"></span>
310
+ <span class="font-label-xs text-label-xs text-on-surface-variant uppercase tracking-widest">AI-Powered Authenticity Engine</span>
 
 
 
 
 
 
 
 
 
 
 
 
311
  </div>
312
+
313
+ <!-- Headline -->
314
+ <h1 class="font-h1 text-h1 text-on-surface drop-shadow-md">
315
+ Verify Reality <br class="hidden md:block"/>
316
+ in <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-container to-secondary-container">Real-Time</span>
317
+ </h1>
318
+
319
+ <!-- Subtext -->
320
+ <p class="font-body-base text-body-base text-on-surface-variant max-w-lg mt-sm mb-lg">
321
+ Deploy advanced neural networks to detect deepfakes, synthetic media, and manipulated data streams with military-grade precision. Establish an unbreakable perimeter of truth.
322
+ </p>
323
+
324
+ <!-- CTAs -->
325
+ <div class="flex flex-wrap items-center gap-md mt-sm">
326
+ <button onclick="showState('uploadSection')" class="group relative px-lg py-sm bg-primary-container text-on-primary font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT overflow-hidden transition-all duration-300 hover:shadow-[0_0_30px_rgba(0,255,156,0.4)] active:scale-95">
327
+ <span class="relative z-10 flex items-center gap-2">
328
+ Analyze Video
329
+ <span class="material-symbols-outlined text-[16px]">radar</span>
330
+ </span>
331
+ <div class="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/40 to-transparent group-hover:[animation:shimmer_1s_infinite]"></div>
332
+ </button>
333
+ <button class="px-lg py-sm bg-transparent border border-surface-tint/50 text-surface-tint font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT hover:bg-surface-tint/10 hover:border-surface-tint transition-all duration-300 active:scale-95">
334
+ View Demo
335
+ </button>
336
+ </div>
337
+
338
+ <!-- Stats -->
339
+ <div class="flex items-center gap-lg mt-xl pt-lg border-t border-outline/20 w-full max-w-md">
340
+ <div>
341
+ <div class="font-data-mono text-data-mono text-primary-container">99.9%</div>
342
+ <div class="font-label-xs text-[10px] text-outline mt-1 uppercase">Accuracy Rate</div>
343
+ </div>
344
+ <div class="w-px h-8 bg-outline/30"></div>
345
+ <div>
346
+ <div class="font-data-mono text-data-mono text-secondary-container">&lt;15ms</div>
347
+ <div class="font-label-xs text-[10px] text-outline mt-1 uppercase">Latency Ping</div>
348
+ </div>
349
  </div>
350
  </div>
351
+
352
+ <!-- Right: Orb visualizer -->
353
+ <div class="relative w-full aspect-square max-w-lg mx-auto flex items-center justify-center lg:justify-end">
354
+ <div class="absolute inset-0 bg-surface-container-low/20 backdrop-blur-3xl rounded-full border border-outline/10 shadow-[inset_0_0_40px_rgba(0,0,0,0.8)] flex items-center justify-center overflow-hidden">
355
+ <!-- Spinning rings -->
356
+ <div class="absolute w-[120%] h-[120%] border border-primary-container/10 rounded-full animate-[spin_20s_linear_infinite]"></div>
357
+ <div class="absolute w-[100%] h-[100%] border border-secondary-container/20 rounded-full border-dashed animate-[spin_15s_linear_infinite_reverse]"></div>
358
+ <div class="absolute w-[80%] h-[80%] border-2 border-primary-container/5 rounded-full"></div>
359
+ <!-- Core orb -->
360
+ <div class="relative w-3/4 h-3/4 rounded-full overflow-hidden mix-blend-screen shadow-[0_0_60px_rgba(0,255,156,0.2)] bg-gradient-to-br from-primary-container/20 via-secondary-container/10 to-transparent flex items-center justify-center">
361
+ <span class="material-symbols-outlined text-primary-container" style="font-size:120px;opacity:0.4;font-variation-settings:'FILL' 1;">radar</span>
362
+ <div class="absolute inset-0 bg-gradient-to-t from-background via-transparent to-transparent"></div>
363
+ </div>
364
+ </div>
365
+ <!-- Floating HUD -->
366
+ <div class="absolute top-10 -left-4 bg-surface/80 backdrop-blur-md border border-outline/30 px-sm py-1 font-data-mono text-[10px] text-primary-container">SYS.OPT.OK</div>
367
+ <div class="absolute bottom-20 -right-4 bg-surface/80 backdrop-blur-md border border-outline/30 px-sm py-1 font-data-mono text-[10px] text-secondary-container flex items-center gap-1">
368
+ <span class="material-symbols-outlined text-[12px]">sync</span> LIVE
369
+ </div>
370
  </div>
371
  </div>
372
+ </section>
373
+
374
+ <!-- ═══════════════════════════════════════════════════════════
375
+ STATE 2: UPLOAD SECTION
376
+ ════════════════════════════════════════════════════════════ -->
377
+ <section id="uploadSection" style="display:none;" class="relative z-10 flex flex-col items-center justify-center pt-28 pb-24 px-6 min-h-screen">
378
+ <div class="w-full max-w-3xl flex flex-col gap-8">
379
+
380
+ <!-- Header -->
381
+ <div class="text-center space-y-2">
382
+ <h1 class="font-h2 text-h2 text-primary-container drop-shadow-[0_0_12px_rgba(0,255,156,0.4)]">SECURE INGEST PORTAL</h1>
383
+ <p class="font-data-mono text-data-mono text-on-surface-variant">Awaiting encrypted payload via protocol AX-9.</p>
 
384
  </div>
 
385
 
386
+ <!-- Drop zone -->
387
+ <div class="relative bg-surface-container/40 backdrop-blur-2xl border border-outline/20 rounded-xl overflow-hidden group hover:border-primary-container/50 transition-colors duration-500 shadow-[inset_0_1px_1px_rgba(255,255,255,0.05),0_0_30px_rgba(0,0,0,0.5)]">
388
+ <div class="scan-line"></div>
389
+ <div id="dropZoneInner"
390
+ onclick="document.getElementById('fileInput').click()"
391
+ class="p-10 flex flex-col items-center justify-center border-2 border-dashed border-outline-variant/50 m-4 rounded-lg bg-surface-container-low/30 hover:bg-primary-container/5 hover:border-primary-container hover:shadow-[0_0_40px_rgba(0,255,156,0.1)_inset] transition-all duration-300 cursor-pointer min-h-[300px]">
392
+
393
+ <!-- Upload prompt (default) -->
394
+ <div id="uploadPrompt">
395
+ <div class="w-24 h-24 rounded-full bg-surface-variant flex items-center justify-center mb-6 shadow-[inset_0_2px_4px_rgba(0,0,0,0.4)] group-hover:bg-surface-container-highest transition-colors mx-auto">
396
+ <span class="material-symbols-outlined text-[48px] text-on-surface-variant group-hover:text-primary-container group-hover:drop-shadow-[0_0_12px_rgba(0,255,156,0.8)] transition-all duration-300" style="font-variation-settings:'FILL' 1;">cloud_upload</span>
397
+ </div>
398
+ <h3 class="font-h3 text-h3 text-on-surface mb-2 text-center">INITIALIZE UPLOAD</h3>
399
+ <p class="font-body-base text-body-base text-on-surface-variant mb-6 text-center max-w-sm">Drag and drop your video file here, or click to browse.</p>
400
+ <div class="flex items-center gap-2 font-label-xs text-label-xs text-outline tracking-widest justify-center">
401
+ <span class="w-1.5 h-1.5 rounded-full bg-outline"></span>
402
+ ACCEPTED: MP4 Β· AVI Β· MOV Β· MKV Β· WebM
403
+ <span class="w-1.5 h-1.5 rounded-full bg-outline"></span>
404
  </div>
405
  </div>
406
+
407
+ <!-- File chosen state (hidden until file selected) -->
408
+ <div id="fileChosen" style="display:none;" class="w-full flex items-center gap-5">
409
+ <div class="w-14 h-14 rounded-lg bg-primary-container/10 border border-primary-container/30 flex items-center justify-center flex-shrink-0 shadow-[0_0_20px_rgba(0,255,156,0.15)]">
410
+ <span class="material-symbols-outlined text-primary-container text-[28px]" style="font-variation-settings:'FILL' 1;">video_file</span>
411
+ </div>
412
+ <div class="flex-1 min-w-0">
413
+ <p id="chosenName" class="font-data-mono text-data-mono text-on-surface overflow-hidden text-ellipsis whitespace-nowrap"></p>
414
+ <p id="chosenSize" class="font-label-xs text-label-xs text-on-surface-variant mt-1"></p>
415
+ </div>
416
+ <button id="clearFileBtn" onclick="clearFile(event)" class="p-2 text-on-surface-variant hover:text-error hover:bg-error/10 rounded-sm transition-all">
417
+ <span class="material-symbols-outlined text-[20px]">close</span>
418
+ </button>
 
419
  </div>
 
 
 
420
  </div>
421
  </div>
422
+
423
+ <!-- Hidden file input -->
424
  <input type="file" id="fileInput" accept="video/*" style="display:none;"/>
425
+
426
+ <!-- Analyze button -->
427
+ <button id="analyzeBtn" disabled onclick="analyzeVideo()"
428
+ class="w-full py-4 font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT transition-all duration-300 border border-outline/30 bg-surface-container/40 text-outline cursor-not-allowed"
429
+ style="pointer-events:none;">
430
+ <span class="flex items-center justify-center gap-2">
431
+ <span class="material-symbols-outlined text-[16px]">play_arrow</span>
432
+ INITIATE ANALYSIS
433
+ </span>
434
+ </button>
435
+
436
+ <!-- Active queue -->
437
+ <div class="flex flex-col gap-4">
438
+ <div class="flex items-center justify-between border-b border-outline/20 pb-2">
439
+ <h4 class="font-label-xs text-label-xs text-primary-container uppercase tracking-widest flex items-center gap-2">
440
+ <span class="w-2 h-2 bg-primary-container shadow-[0_0_8px_rgba(0,255,156,0.8)]"></span>
441
+ ACTIVE QUEUE
442
+ </h4>
443
+ <span id="queueTime" class="font-data-mono text-[10px] text-on-surface-variant">SYS_TIME: --:--:--</span>
 
 
 
 
444
  </div>
445
+ <div id="queueList" class="grid grid-cols-1 gap-2">
446
+ <div class="bg-surface-container-low border border-outline-variant/30 rounded-DEFAULT p-4 flex items-center gap-3 opacity-40">
447
+ <span class="material-symbols-outlined text-outline text-[20px]">inbox</span>
448
+ <span class="font-data-mono text-data-mono text-on-surface-variant">No payload queued. Awaiting upload.</span>
 
 
 
 
449
  </div>
450
  </div>
451
+ </div>
452
+
453
+ <!-- Back to hero -->
454
+ <div class="text-center">
455
+ <button onclick="showState('heroSection')" class="font-data-mono text-data-mono text-on-surface-variant hover:text-primary-container transition-colors flex items-center gap-2 mx-auto">
456
+ <span class="material-symbols-outlined text-[16px]">arrow_back</span>
457
+ Back to Command Center
458
+ </button>
459
+ </div>
460
+ </div>
461
+ </section>
462
+
463
+ <!-- ═══════════════════════════════════════════════════════════
464
+ STATE 3: PROCESSING SECTION
465
+ ════════════════════════════════════════════════════════════ -->
466
+ <section id="processingSection" style="display:none;" class="relative z-10 flex flex-col items-center justify-center px-6 min-h-screen">
467
+ <div class="absolute inset-0 noise-bg z-0"></div>
468
+ <div class="scan-line" style="top:30%"></div>
469
+
470
+ <div class="relative z-10 container mx-auto px-6 flex flex-col items-center justify-center w-full max-w-4xl pt-24 pb-16">
471
+
472
+ <!-- Header -->
473
+ <div class="w-full max-w-3xl mb-xl text-center">
474
+ <h1 class="font-h1 text-h1 text-primary-container drop-shadow-[0_0_8px_rgba(0,255,156,0.3)] mb-sm">SYSTEM_STATE: ANALYSIS</h1>
475
+ <p class="font-data-mono text-data-mono text-primary-fixed-dim/70 tracking-[0.2em] uppercase">Initiating Deep Inspection Protocol</p>
476
+ </div>
477
+
478
+ <!-- Bento grid -->
479
+ <div class="w-full grid grid-cols-1 md:grid-cols-12 gap-gutter">
480
+
481
+ <!-- Left: Orb visual -->
482
+ <div class="md:col-span-5 glass-panel rounded-xl p-lg flex flex-col items-center justify-center relative min-h-[300px] border-outline/20">
483
+ <div class="absolute top-sm left-sm font-label-xs text-label-xs text-outline tracking-widest uppercase">Target Vector</div>
484
+ <div class="relative w-48 h-48 rounded-full border border-primary-container/30 shadow-[0_0_30px_rgba(0,255,156,0.1)] flex items-center justify-center">
485
+ <div class="absolute inset-2 rounded-full border border-dashed border-primary-container/40 animate-[spin_8s_linear_infinite]"></div>
486
+ <div class="absolute inset-6 rounded-full bg-surface-container-high shadow-inner flex items-center justify-center overflow-hidden bg-gradient-to-br from-primary-container/10 to-secondary-container/5">
487
+ <span class="material-symbols-outlined text-secondary-container text-[48px] opacity-50" style="font-variation-settings:'FILL' 1;">analytics</span>
488
+ </div>
489
+ <div class="absolute inset-0 rounded-full border-t-2 border-primary-container/80 blur-[2px] animate-[spin_3s_linear_infinite]"></div>
490
+ <span class="material-symbols-outlined absolute text-primary-container text-4xl drop-shadow-[0_0_10px_rgba(0,255,156,0.8)]">troubleshoot</span>
491
  </div>
492
+ <div class="mt-lg text-center w-full">
493
+ <div class="font-data-mono text-data-mono text-on-surface-variant flex items-center justify-between w-full border-b border-outline/10 pb-xs mb-xs">
494
+ <span>DATA_STREAM</span>
495
+ <span class="text-secondary-fixed">ACTIVE</span>
496
+ </div>
497
+ <div class="font-data-mono text-data-mono text-on-surface-variant flex items-center justify-between w-full border-b border-outline/10 pb-xs">
498
+ <span>THROUGHPUT</span>
499
+ <span class="text-tertiary-container" id="throughputVal">2.4 TB/s</span>
500
+ </div>
501
  </div>
502
  </div>
503
+
504
+ <!-- Right: Pipeline steps -->
505
+ <div class="md:col-span-7 glass-panel rounded-xl p-lg flex flex-col justify-center gap-md border-outline/20">
506
+ <div class="flex justify-between items-center mb-sm">
507
+ <div class="font-label-xs text-label-xs text-outline tracking-widest uppercase">Processing Pipeline</div>
508
+ <div id="pipelinePercent" class="font-data-mono text-data-mono text-primary-container bg-primary-container/10 px-2 py-1 rounded">0% COMPLETE</div>
509
  </div>
 
 
 
510
 
511
+ <!-- Step 0: Extract frames -->
512
+ <div id="step0" class="pipeline-step pending flex flex-col gap-xs">
513
+ <div class="flex justify-between items-center">
514
+ <div class="flex items-center gap-sm">
515
+ <span id="step0icon" class="material-symbols-outlined text-outline text-sm">hourglass_empty</span>
516
+ <span class="font-data-mono text-data-mono text-on-surface">Extracting frames...</span>
517
+ </div>
518
+ <span id="step0pct" class="font-data-mono text-data-mono text-outline">0%</span>
519
+ </div>
520
+ <div class="w-full h-2 bg-surface-container-highest flex gap-[2px]" id="step0bar">
521
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
522
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
523
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
524
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
525
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
526
+ </div>
527
+ </div>
528
 
529
+ <!-- Step 1: Detect faces -->
530
+ <div id="step1" class="pipeline-step pending flex flex-col gap-xs">
531
+ <div class="flex justify-between items-center">
532
+ <div class="flex items-center gap-sm">
533
+ <span id="step1icon" class="material-symbols-outlined text-outline text-sm">hourglass_empty</span>
534
+ <span class="font-data-mono text-data-mono text-on-surface">Detecting faces...</span>
535
+ </div>
536
+ <span id="step1pct" class="font-data-mono text-data-mono text-outline">0%</span>
537
+ </div>
538
+ <div class="w-full h-2 bg-surface-container-highest flex gap-[2px]" id="step1bar">
539
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
540
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
541
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
542
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
543
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
544
+ </div>
545
  </div>
546
+
547
+ <!-- Step 2: Model inference -->
548
+ <div id="step2" class="pipeline-step pending flex flex-col gap-xs">
549
+ <div class="flex justify-between items-center">
550
+ <div class="flex items-center gap-sm">
551
+ <span id="step2icon" class="material-symbols-outlined text-outline text-sm">hourglass_empty</span>
552
+ <span class="font-data-mono text-data-mono text-on-surface">Running model inference...</span>
553
+ </div>
554
+ <span id="step2pct" class="font-data-mono text-data-mono text-outline">0%</span>
555
+ </div>
556
+ <div class="w-full h-2 bg-surface-container-highest flex gap-[2px]" id="step2bar">
557
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
558
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
559
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
560
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
561
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
562
+ </div>
563
  </div>
564
+
565
+ <!-- Step 3: Audio patterns -->
566
+ <div id="step3" class="pipeline-step pending flex flex-col gap-xs mt-sm">
567
+ <div class="flex justify-between items-center">
568
+ <div class="flex items-center gap-sm">
569
+ <span id="step3icon" class="material-symbols-outlined text-outline text-sm">hourglass_empty</span>
570
+ <span class="font-data-mono text-data-mono text-on-surface">Analyzing audio patterns...</span>
571
+ </div>
572
+ <span id="step3pct" class="font-data-mono text-data-mono text-outline">0%</span>
573
+ </div>
574
+ <div class="w-full h-2 bg-surface-container-highest flex gap-[2px]" id="step3bar">
575
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
576
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
577
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
578
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
579
+ <div class="h-full bg-primary-container/20 flex-1"></div><div class="h-full bg-primary-container/20 flex-1"></div>
580
+ </div>
581
  </div>
582
+
583
+ <!-- CLI feedback -->
584
+ <div class="mt-auto bg-surface-container-highest/50 border border-outline/10 p-sm rounded font-data-mono text-data-mono text-xs text-primary-fixed-dim/60 shadow-inner min-h-[60px]" id="cliFeedback">
585
+ &gt; [SYS] Initializing analysis pipeline...<br/>
586
+ &gt; [SYS] Loading neural weights... <span class="text-primary-container animate-pulse">_</span>
587
  </div>
588
+ </div>
589
+ </div>
590
+ </div>
591
+ </section>
592
+
593
+ <!-- ═══════════════════════════════════════════════════════════
594
+ STATE 4: RESULTS SECTION
595
+ ════════════════════════════════════════════════════════════ -->
596
+ <section id="resultsSection" style="display:none;" class="relative z-10 pt-24 pb-xl px-gutter max-w-[1400px] mx-auto w-full flex flex-col gap-lg">
597
+
598
+ <!-- Cinematic banner -->
599
+ <div id="resultBanner" class="glass-panel glow-border-primary rounded-xl relative overflow-hidden flex flex-col md:flex-row items-center justify-between p-lg md:p-margin min-h-[280px]">
600
+ <div class="scanline"></div>
601
+ <!-- BG overlay -->
602
+ <div class="absolute inset-0 opacity-10 mix-blend-overlay pointer-events-none bg-gradient-to-br from-primary-container/20 via-transparent to-secondary-container/10"></div>
603
+
604
+ <div class="relative z-10 flex flex-col md:flex-row items-center gap-margin w-full">
605
+ <!-- Circular verdict badge -->
606
+ <div id="verdictBadge" class="relative w-44 h-44 rounded-full border-4 border-primary-container flex items-center justify-center shadow-[0_0_30px_rgba(0,255,156,0.4)] flex-shrink-0">
607
+ <div class="absolute inset-0 rounded-full border border-primary-container animate-[spin_10s_linear_infinite] opacity-50"></div>
608
+ <div class="absolute inset-2 rounded-full border border-dashed border-primary-container animate-[spin_15s_linear_infinite_reverse] opacity-30"></div>
609
+ <div class="text-center">
610
+ <span id="verdictIcon" class="material-symbols-outlined text-primary-container text-5xl mb-1" style="font-variation-settings:'FILL' 1;">verified_user</span>
611
+ <h2 id="verdictLabel" class="font-h3 text-h3 text-primary-container uppercase tracking-widest drop-shadow-[0_0_5px_rgba(0,255,156,0.8)] text-sm">AUTHENTIC</h2>
612
  </div>
613
  </div>
614
+
615
+ <!-- Confidence score -->
616
+ <div class="flex-grow text-center md:text-left flex flex-col gap-sm">
617
+ <p id="resultId" class="font-data-mono text-data-mono text-primary-container/80 uppercase tracking-widest">Analysis Complete // ID: ----</p>
618
+ <h1 class="font-h1 text-h1 text-on-surface">Confidence Score</h1>
619
+ <div id="confidenceScore" class="font-h1 leading-none text-primary-container drop-shadow-[0_0_15px_rgba(0,255,156,0.6)] font-black" style="font-size:80px;">--.--%</div>
620
+ </div>
621
  </div>
622
+ </div>
623
 
624
+ <!-- Main grid: insights + diagnostics -->
625
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-lg">
626
+
627
+ <!-- Left: Primary Vectors -->
628
+ <div class="lg:col-span-4 flex flex-col gap-md">
629
+ <h3 class="font-label-xs text-label-xs text-outline uppercase tracking-widest border-b border-surface-variant pb-2">Primary Vectors</h3>
630
+ <div id="insightCards" class="flex flex-col gap-md">
631
+ <!-- Populated by JS -->
632
  </div>
633
+ </div>
634
+
635
+ <!-- Right: System Diagnostics -->
636
+ <div class="lg:col-span-8 flex flex-col gap-md">
637
+ <h3 class="font-label-xs text-label-xs text-outline uppercase tracking-widest border-b border-surface-variant pb-2">System Diagnostics</h3>
638
+ <div class="glass-panel rounded-lg flex-grow p-md font-data-mono text-data-mono text-sm relative overflow-hidden flex flex-col min-h-[280px]">
639
+ <div class="scanline"></div>
640
+ <div class="flex justify-between items-center border-b border-surface-variant/50 pb-sm mb-sm text-on-surface-variant">
641
+ <span>METADATA_STREAM</span>
642
+ <span class="text-primary-container animate-pulse">ACTIVE</span>
643
  </div>
644
+ <div id="metaTerminal" class="flex-grow space-y-2 text-on-surface-variant overflow-y-auto pr-2">
645
+ <!-- Populated by JS -->
 
646
  </div>
647
  </div>
648
  </div>
649
+ </div>
650
 
651
+ <!-- Timeline -->
652
+ <div class="glass-panel rounded-xl p-lg flex flex-col gap-md">
653
+ <h3 class="font-label-xs text-label-xs text-outline uppercase tracking-widest border-b border-surface-variant pb-2">Probability Timeline</h3>
654
+ <div class="relative">
655
+ <!-- Grid lines -->
656
+ <div class="absolute inset-0 flex flex-col justify-between pointer-events-none opacity-20 pb-6">
657
+ <div class="w-full h-px bg-outline"></div>
658
+ <div class="w-full h-px bg-outline"></div>
659
+ <div class="w-full h-px bg-outline"></div>
660
+ <div class="w-full h-px bg-outline"></div>
661
+ </div>
662
+ <!-- Y-axis labels -->
663
+ <div class="absolute left-0 top-0 h-[160px] flex flex-col justify-between pointer-events-none pr-2">
664
+ <span class="font-data-mono text-[9px] text-outline">100%</span>
665
+ <span class="font-data-mono text-[9px] text-outline">75%</span>
666
+ <span class="font-data-mono text-[9px] text-outline">50%</span>
667
+ <span class="font-data-mono text-[9px] text-outline">25%</span>
668
+ <span class="font-data-mono text-[9px] text-outline">0%</span>
669
+ </div>
670
+ <!-- Bars container -->
671
+ <div id="timelineBars" class="h-[160px] flex items-end gap-[2px] border-b border-l border-surface-variant pl-8 relative z-10 overflow-hidden">
672
+ <!-- Populated by JS -->
673
  </div>
674
  </div>
675
+ <div id="timelineLabels" class="flex justify-between font-data-mono text-data-mono text-xs text-outline mt-1 pl-8">
676
+ <!-- Populated by JS -->
 
 
 
 
677
  </div>
678
+ </div>
679
 
680
+ <!-- Audio section (shown only when audio.available) -->
681
+ <div id="audioSection" style="display:none;" class="glass-panel rounded-xl p-lg flex flex-col gap-md">
682
+ <h3 class="font-label-xs text-label-xs text-outline uppercase tracking-widest border-b border-surface-variant pb-2 flex items-center gap-2">
683
+ <span class="material-symbols-outlined text-[14px]">graphic_eq</span>
684
+ Audio Analysis
685
+ </h3>
686
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-md">
687
+ <!-- Audio verdict -->
688
+ <div class="glass-panel rounded-lg p-md flex items-center gap-md">
689
+ <div class="w-12 h-12 rounded-full bg-surface-container-highest flex items-center justify-center flex-shrink-0 border border-surface-variant">
690
+ <span id="audioIcon" class="material-symbols-outlined text-primary-container" style="font-variation-settings:'FILL' 1;">record_voice_over</span>
691
+ </div>
692
+ <div>
693
+ <div class="font-label-xs text-label-xs text-outline uppercase tracking-widest mb-1">Voice Classification</div>
694
+ <div id="audioVerdict" class="font-h3 text-h3 text-primary-container">HUMAN_VOICE</div>
695
+ <div id="audioConfidence" class="font-data-mono text-data-mono text-on-surface-variant mt-1">Confidence: --%</div>
696
+ </div>
697
+ </div>
698
+ <!-- Audio scores -->
699
+ <div class="glass-panel rounded-lg p-md flex flex-col gap-sm">
700
+ <div class="font-label-xs text-label-xs text-outline uppercase tracking-widest mb-1">Score Breakdown</div>
701
+ <div class="flex items-center justify-between">
702
+ <span class="font-data-mono text-data-mono text-on-surface-variant text-xs">Model Score</span>
703
+ <div class="flex items-center gap-2 flex-1 ml-4">
704
+ <div class="h-1 flex-grow bg-surface-container-highest rounded-full overflow-hidden">
705
+ <div id="audioModelBar" class="h-full bg-primary-container" style="width:0%"></div>
706
+ </div>
707
+ <span id="audioModelScore" class="font-data-mono text-data-mono text-primary-container text-xs w-10 text-right">--%</span>
708
+ </div>
709
+ </div>
710
+ <div class="flex items-center justify-between">
711
+ <span class="font-data-mono text-data-mono text-on-surface-variant text-xs">Heuristic Score</span>
712
+ <div class="flex items-center gap-2 flex-1 ml-4">
713
+ <div class="h-1 flex-grow bg-surface-container-highest rounded-full overflow-hidden">
714
+ <div id="audioHeuristicBar" class="h-full bg-secondary-container" style="width:0%"></div>
715
+ </div>
716
+ <span id="audioHeuristicScore" class="font-data-mono text-data-mono text-secondary-container text-xs w-10 text-right">--%</span>
717
+ </div>
718
+ </div>
719
+ </div>
720
  </div>
721
+ <!-- Audio details -->
722
+ <div id="audioDetails" class="flex flex-col gap-2">
723
+ <!-- Populated by JS -->
724
  </div>
725
+ </div>
726
 
727
+ <!-- New analysis button -->
728
+ <div class="text-center mt-sm">
729
+ <button onclick="resetAll()" class="font-data-mono text-data-mono text-error border border-error/50 px-lg py-sm rounded bg-error/5 hover:bg-error/10 hover:shadow-[0_0_15px_rgba(255,180,171,0.2)] transition-all flex items-center gap-xs mx-auto">
730
+ <span class="material-symbols-outlined text-sm">restart_alt</span>
731
+ NEW ANALYSIS
732
+ </button>
733
+ </div>
734
+ </section>
735
 
736
+ <!-- ═══════════════════════════════════════════════════════════
737
+ FOOTER
738
+ ════════════════════════════════════════════════════════════ -->
739
+ <footer class="relative z-10 w-full py-8 px-10 flex flex-col md:flex-row justify-between items-center opacity-80 bg-black border-t border-emerald-900/30 font-mono text-[10px] uppercase tracking-[0.2em]">
740
+ <div class="text-emerald-500 mb-4 md:mb-0">Β© 2024 AUTHRIX COMMAND. SYSTEM_STATE: VIGILANT</div>
741
+ <div class="flex items-center gap-md">
742
+ <a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Kernel</a>
743
+ <a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Protocol</a>
744
+ <a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Override</a>
745
+ <a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Diagnostics</a>
746
+ </div>
747
  </footer>
748
 
749
+ <!-- ═══════════════════════════════════════════════════════════
750
+ JAVASCRIPT
751
+ ════════════════════════════════════════════════════════════ -->
752
  <script>
753
+ // ── API base ─────────────────────────────────────────────────────────────────
754
+ const API_BASE = (window.location.protocol === 'file:')
755
+ ? 'http://localhost:8000'
756
+ : window.location.origin;
757
+
758
+ let selectedFile = null;
759
+ let pipelineTimers = [];
760
+
761
+ // ── Init ──────────────────────────────────────────────────────────────────────
762
+ window.addEventListener('load', () => {
763
+ pingHealth();
764
+ initUpload();
765
+ startQueueClock();
766
+ });
767
+
768
+ // ── Health check ──────────────────────────────────────────────────────────────
769
+ async function pingHealth() {
770
+ const badge = document.getElementById('healthBadge');
771
+ const dot = document.getElementById('healthDot');
772
+ const text = document.getElementById('healthText');
773
+ badge.style.display = 'flex';
774
+ try {
775
+ const res = await fetch(API_BASE + '/health');
776
+ const data = await res.json();
777
+ if (data.ready) {
778
+ dot.className = 'w-2 h-2 rounded-full bg-primary-container shadow-[0_0_8px_#00ff9c] animate-pulse';
779
+ text.textContent = data.model ? data.model.toUpperCase().slice(0, 20) : 'READY';
780
+ text.className = 'font-label-xs text-label-xs text-primary-container';
781
+ } else {
782
+ dot.className = 'w-2 h-2 rounded-full bg-tertiary-container animate-pulse';
783
+ text.textContent = 'LOADING';
784
+ text.className = 'font-label-xs text-label-xs text-tertiary-container';
785
+ }
786
+ } catch {
787
+ dot.className = 'w-2 h-2 rounded-full bg-error';
788
+ text.textContent = 'OFFLINE';
789
+ text.className = 'font-label-xs text-label-xs text-error';
790
+ }
791
+ }
792
+
793
+ // ── Show/hide states ──────────────────────────────────────────────────────────
794
+ function showState(id) {
795
+ ['heroSection', 'uploadSection', 'processingSection', 'resultsSection'].forEach(s => {
796
+ const el = document.getElementById(s);
797
+ if (el) el.style.display = (s === id) ? '' : 'none';
798
+ });
799
+ window.scrollTo({ top: 0, behavior: 'smooth' });
800
+ }
801
+
802
+ // ── Queue clock ───────────────────────────────────────────────────────────────
803
+ function startQueueClock() {
804
+ function tick() {
805
+ const el = document.getElementById('queueTime');
806
+ if (el) {
807
+ const now = new Date();
808
+ const hh = String(now.getHours()).padStart(2, '0');
809
+ const mm = String(now.getMinutes()).padStart(2, '0');
810
+ const ss = String(now.getSeconds()).padStart(2, '0');
811
+ el.textContent = `SYS_TIME: ${hh}:${mm}:${ss}`;
812
  }
 
813
  }
814
+ tick();
815
+ setInterval(tick, 1000);
816
+ }
817
+
818
+ // ── Upload init ───────────────────────────────────────────────────────────────
819
+ function initUpload() {
820
+ const zone = document.getElementById('dropZoneInner');
821
+ const input = document.getElementById('fileInput');
822
+
823
+ // Drag events
824
+ zone.addEventListener('dragover', e => {
825
+ e.preventDefault();
826
+ zone.classList.add('drag-over');
827
+ });
828
+ zone.addEventListener('dragleave', () => zone.classList.remove('drag-over'));
829
+ zone.addEventListener('drop', e => {
830
+ e.preventDefault();
831
+ zone.classList.remove('drag-over');
832
+ const file = e.dataTransfer.files[0];
833
+ if (file) applyFile(file);
834
+ });
835
+
836
+ // File input change
837
+ input.addEventListener('change', () => {
838
+ if (input.files[0]) applyFile(input.files[0]);
839
+ });
840
+ }
841
+
842
+ // ── Apply selected file ───────────────────────────────────────────────────────
843
+ function applyFile(file) {
844
+ selectedFile = file;
845
+
846
+ // Show file info in drop zone
847
+ document.getElementById('uploadPrompt').style.display = 'none';
848
+ document.getElementById('fileChosen').style.display = 'flex';
849
+ document.getElementById('chosenName').textContent = file.name;
850
+ document.getElementById('chosenSize').textContent = formatBytes(file.size);
851
+
852
+ // Enable analyze button
853
+ const btn = document.getElementById('analyzeBtn');
854
+ btn.disabled = false;
855
+ btn.style.pointerEvents = '';
856
+ btn.className = 'w-full py-4 font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT transition-all duration-300 bg-primary-container text-on-primary cursor-pointer hover:shadow-[0_0_30px_rgba(0,255,156,0.4)] active:scale-95';
857
+
858
+ // Update queue
859
+ updateQueue(file);
860
+ }
861
+
862
+ // ── Clear file ────────────────────────────────────────────────────────────────
863
+ function clearFile(e) {
864
+ e.stopPropagation();
865
+ selectedFile = null;
866
+ document.getElementById('fileInput').value = '';
867
+ document.getElementById('uploadPrompt').style.display = '';
868
+ document.getElementById('fileChosen').style.display = 'none';
869
+
870
+ const btn = document.getElementById('analyzeBtn');
871
+ btn.disabled = true;
872
+ btn.style.pointerEvents = 'none';
873
+ btn.className = 'w-full py-4 font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT transition-all duration-300 border border-outline/30 bg-surface-container/40 text-outline cursor-not-allowed';
874
+
875
+ // Reset queue
876
+ document.getElementById('queueList').innerHTML = `
877
+ <div class="bg-surface-container-low border border-outline-variant/30 rounded-DEFAULT p-4 flex items-center gap-3 opacity-40">
878
+ <span class="material-symbols-outlined text-outline text-[20px]">inbox</span>
879
+ <span class="font-data-mono text-data-mono text-on-surface-variant">No payload queued. Awaiting upload.</span>
880
+ </div>`;
881
+ }
882
+
883
+ // ── Update queue display ──────────────────────────────────────────────────────
884
+ function updateQueue(file) {
885
+ document.getElementById('queueList').innerHTML = `
886
+ <div class="bg-surface-container-low border border-outline-variant/30 rounded-DEFAULT p-4 flex flex-col gap-3 relative overflow-hidden">
887
+ <div class="absolute bottom-0 left-0 h-[1px] bg-primary-container w-full shadow-[0_0_5px_rgba(0,255,156,0.8)]"></div>
888
+ <div class="flex justify-between items-start">
889
+ <div class="flex items-center gap-3">
890
+ <span class="material-symbols-outlined text-primary-container text-[20px]" style="font-variation-settings:'FILL' 1;">video_file</span>
891
+ <div>
892
+ <div class="font-data-mono text-data-mono text-on-surface">${escHtml(file.name)}</div>
893
+ <div class="font-label-xs text-[10px] text-on-surface-variant mt-1">${formatBytes(file.size)}</div>
894
+ </div>
895
+ </div>
896
+ <span class="font-label-xs text-[10px] border border-primary-container/50 text-primary-container px-2 py-1 bg-primary-container/10">READY</span>
897
+ </div>
898
+ </div>`;
899
+ }
900
+
901
+ // ── Analyze video ─────────────────────────────────────────────────────────────
902
+ async function analyzeVideo() {
903
+ if (!selectedFile) return;
904
+
905
+ showState('processingSection');
906
+ resetPipeline();
907
+ startPipelineAnimation();
908
+
909
+ const fd = new FormData();
910
+ fd.append('file', selectedFile);
911
+
912
+ try {
913
+ const res = await fetch(API_BASE + '/analyze', { method: 'POST', body: fd });
914
+ if (!res.ok) {
915
+ const err = await res.json().catch(() => ({ detail: res.statusText }));
916
+ throw new Error(err.detail || `HTTP ${res.status}`);
917
+ }
918
+ const data = await res.json();
919
+ stopPipelineAnimation();
920
+ renderResults(data);
921
+ showState('resultsSection');
922
+ } catch (err) {
923
+ stopPipelineAnimation();
924
+ showState('uploadSection');
925
+ alert('Analysis failed: ' + err.message);
926
+ }
927
+ }
928
+
929
+ // ── Pipeline animation ────────────────────────────────────────────────────────
930
+ const STEP_ICONS = ['filter_frames', 'face', 'memory', 'graphic_eq'];
931
+ const STEP_DELAYS = [0, 1800, 3600, 5400];
932
+ const STEP_PERCENTS = [25, 50, 75, 100];
933
+ const CLI_MSGS = [
934
+ ['> [SYS] Decoding video container...', '> [SYS] Sampling keyframes... OK'],
935
+ ['> [DET] Loading face detector...', '> [DET] Scanning for facial regions... OK'],
936
+ ['> [INF] Allocating tensor buffers...', '> [INF] Running ViT ensemble inference...', '> [INF] Batch processing... <span class="text-primary-container animate-pulse">_</span>'],
937
+ ['> [AUD] Extracting audio track...', '> [AUD] Computing spectral features...', '> [AUD] Classifying voice patterns... <span class="text-primary-container animate-pulse">_</span>'],
938
+ ];
939
+
940
+ function resetPipeline() {
941
+ for (let i = 0; i < 4; i++) {
942
+ const step = document.getElementById('step' + i);
943
+ step.className = 'pipeline-step pending flex flex-col gap-xs' + (i === 3 ? ' mt-sm' : '');
944
+ document.getElementById('step' + i + 'icon').className = 'material-symbols-outlined text-outline text-sm';
945
+ document.getElementById('step' + i + 'icon').textContent = 'hourglass_empty';
946
+ document.getElementById('step' + i + 'pct').textContent = '0%';
947
+ document.getElementById('step' + i + 'pct').className = 'font-data-mono text-data-mono text-outline';
948
+ setBarFill(i, 0);
949
+ }
950
+ document.getElementById('pipelinePercent').textContent = '0% COMPLETE';
951
+ document.getElementById('cliFeedback').innerHTML =
952
+ '> [SYS] Initializing analysis pipeline...<br/>> [SYS] Loading neural weights... <span class="text-primary-container animate-pulse">_</span>';
953
+ }
954
+
955
+ function startPipelineAnimation() {
956
+ pipelineTimers = [];
957
+ for (let i = 0; i < 4; i++) {
958
+ const t = setTimeout(() => activateStep(i), STEP_DELAYS[i]);
959
+ pipelineTimers.push(t);
960
+ }
961
+ }
962
+
963
+ function stopPipelineAnimation() {
964
+ pipelineTimers.forEach(t => clearTimeout(t));
965
+ pipelineTimers = [];
966
+ // Mark all done
967
+ for (let i = 0; i < 4; i++) completeStep(i);
968
+ document.getElementById('pipelinePercent').textContent = '100% COMPLETE';
969
+ }
970
+
971
+ function activateStep(idx) {
972
+ // Mark previous as done
973
+ if (idx > 0) completeStep(idx - 1);
974
+
975
+ const step = document.getElementById('step' + idx);
976
+ step.className = 'pipeline-step active flex flex-col gap-xs glow-border-active bg-primary-container/5 p-sm -mx-sm rounded-lg' + (idx === 3 ? ' mt-sm' : '');
977
+
978
+ const icon = document.getElementById('step' + idx + 'icon');
979
+ icon.className = 'material-symbols-outlined text-primary-container text-sm animate-pulse';
980
+ icon.textContent = STEP_ICONS[idx];
981
+
982
+ const pct = document.getElementById('step' + idx + 'pct');
983
+ pct.className = 'font-data-mono text-data-mono text-primary-container';
984
+
985
+ // Animate progress bar fill
986
+ animateBarFill(idx, 0, 80, 1600);
987
+
988
+ // Update percent display
989
+ document.getElementById('pipelinePercent').textContent = STEP_PERCENTS[idx] + '% COMPLETE';
990
+
991
+ // CLI messages
992
+ const cli = document.getElementById('cliFeedback');
993
+ const msgs = CLI_MSGS[idx];
994
+ let html = '';
995
+ msgs.forEach((m, mi) => {
996
+ setTimeout(() => {
997
+ html += (html ? '<br/>' : '') + m;
998
+ cli.innerHTML = html;
999
+ }, mi * 500);
1000
+ });
1001
+ }
1002
+
1003
+ function completeStep(idx) {
1004
+ const step = document.getElementById('step' + idx);
1005
+ step.className = 'pipeline-step done flex flex-col gap-xs' + (idx === 3 ? ' mt-sm' : '');
1006
+
1007
+ const icon = document.getElementById('step' + idx + 'icon');
1008
+ icon.className = 'material-symbols-outlined text-outline text-sm';
1009
+ icon.textContent = 'check_circle';
1010
+
1011
+ const pct = document.getElementById('step' + idx + 'pct');
1012
+ pct.textContent = '100%';
1013
+ pct.className = 'font-data-mono text-data-mono text-outline';
1014
+
1015
+ setBarFill(idx, 100);
1016
+ }
1017
+
1018
+ function setBarFill(idx, pct) {
1019
+ const bar = document.getElementById('step' + idx + 'bar');
1020
+ if (!bar) return;
1021
+ const segments = bar.children;
1022
+ const filled = Math.round((pct / 100) * segments.length);
1023
+ for (let i = 0; i < segments.length; i++) {
1024
+ if (i < filled) {
1025
+ segments[i].className = 'h-full bg-primary-container/80 flex-1 shadow-[0_0_8px_rgba(0,255,156,0.5)]';
1026
+ } else {
1027
+ segments[i].className = 'h-full bg-primary-container/20 flex-1';
1028
+ }
1029
+ }
1030
+ document.getElementById('step' + idx + 'pct').textContent = pct + '%';
1031
+ }
1032
+
1033
+ function animateBarFill(idx, from, to, duration) {
1034
+ const start = performance.now();
1035
+ function frame(now) {
1036
+ const elapsed = now - start;
1037
+ const progress = Math.min(elapsed / duration, 1);
1038
+ const current = from + (to - from) * progress;
1039
+ setBarFill(idx, Math.round(current));
1040
+ if (progress < 1) requestAnimationFrame(frame);
1041
+ }
1042
+ requestAnimationFrame(frame);
1043
+ }
1044
+
1045
+ // ── Render results ────────────────────────────────────────────────────────────
1046
+ function renderResults(data) {
1047
+ const isFake = data.result === 'FAKE';
1048
+ const conf = typeof data.confidence === 'number' ? data.confidence : 0;
1049
+
1050
+ // Banner colors
1051
+ const banner = document.getElementById('resultBanner');
1052
+ banner.className = banner.className.replace(/glow-border-\w+/g, '');
1053
+ banner.classList.add(isFake ? 'glow-border-error' : 'glow-border-primary');
1054
+
1055
+ // Verdict badge
1056
+ const badge = document.getElementById('verdictBadge');
1057
+ if (isFake) {
1058
+ badge.style.borderColor = '#ffb4ab';
1059
+ badge.style.boxShadow = '0 0 30px rgba(255,180,171,0.4)';
1060
+ } else {
1061
+ badge.style.borderColor = '#00ff9c';
1062
+ badge.style.boxShadow = '0 0 30px rgba(0,255,156,0.4)';
1063
+ }
1064
+
1065
+ const verdictIcon = document.getElementById('verdictIcon');
1066
+ const verdictLabel = document.getElementById('verdictLabel');
1067
+ if (isFake) {
1068
+ verdictIcon.textContent = 'gpp_bad';
1069
+ verdictIcon.style.color = '#ffb4ab';
1070
+ verdictLabel.textContent = 'DEEPFAKE';
1071
+ verdictLabel.style.color = '#ffb4ab';
1072
+ } else {
1073
+ verdictIcon.textContent = 'verified_user';
1074
+ verdictIcon.style.color = '#00ff9c';
1075
+ verdictLabel.textContent = 'AUTHENTIC';
1076
+ verdictLabel.style.color = '#00ff9c';
1077
+ }
1078
+
1079
+ // Confidence
1080
+ const confEl = document.getElementById('confidenceScore');
1081
+ confEl.textContent = conf.toFixed(1) + '%';
1082
+ confEl.style.color = isFake ? '#ffb4ab' : '#00ff9c';
1083
+ confEl.style.textShadow = isFake
1084
+ ? '0 0 15px rgba(255,180,171,0.6)'
1085
+ : '0 0 15px rgba(0,255,156,0.6)';
1086
+
1087
+ // Result ID
1088
+ document.getElementById('resultId').textContent =
1089
+ `Analysis Complete // ID: ${Math.random().toString(36).slice(2,10).toUpperCase()}`;
1090
+
1091
+ // Insight cards
1092
+ renderInsightCards(data.details || [], isFake, conf);
1093
+
1094
+ // Metadata terminal
1095
+ renderMetaTerminal(data);
1096
+
1097
+ // Timeline
1098
+ renderTimeline(data.frame_timeline || []);
1099
+
1100
+ // Audio
1101
+ if (data.audio && data.audio.available) {
1102
+ renderAudio(data.audio);
1103
+ document.getElementById('audioSection').style.display = '';
1104
+ } else {
1105
+ document.getElementById('audioSection').style.display = 'none';
1106
+ }
1107
+ }
1108
+
1109
+ // ── Insight cards ─────────────────────────────────────────────────────────────
1110
+ const INSIGHT_ICONS = ['face', 'visibility', 'graphic_eq', 'blur_on', 'analytics'];
1111
+
1112
+ function renderInsightCards(details, isFake, conf) {
1113
+ const container = document.getElementById('insightCards');
1114
+ if (!details.length) {
1115
+ details = ['No detailed analysis available.'];
1116
+ }
1117
+ // Show up to 3 cards
1118
+ const shown = details.slice(0, 3);
1119
+ container.innerHTML = shown.map((detail, i) => {
1120
+ const icon = INSIGHT_ICONS[i % INSIGHT_ICONS.length];
1121
+ // Derive a pseudo-score from confidence + variation
1122
+ const score = Math.max(5, Math.min(99, conf + (i === 0 ? 0 : (i === 1 ? -8 : 12)) + (Math.random() * 6 - 3)));
1123
+ const isRed = isFake && i === 0;
1124
+ const barColor = isRed ? 'bg-error' : 'bg-primary-container';
1125
+ const textColor = isRed ? 'text-error' : 'text-primary-container';
1126
+ const borderHover = isRed ? 'hover:glow-border-error' : 'hover:glow-border-primary';
1127
+ return `
1128
+ <div class="glass-panel rounded-lg p-md flex items-start gap-md ${borderHover} transition-all duration-300 transform hover:-translate-y-1">
1129
+ <div class="w-10 h-10 rounded-full bg-surface-container-highest flex items-center justify-center flex-shrink-0 border border-surface-variant">
1130
+ <span class="material-symbols-outlined ${textColor}" style="font-variation-settings:'FILL' 0;">${icon}</span>
1131
+ </div>
1132
+ <div class="flex-1 min-w-0">
1133
+ <h4 class="font-body-base text-body-base font-semibold text-on-surface">Vector ${i + 1}</h4>
1134
+ <p class="font-data-mono text-data-mono text-on-surface-variant mt-1 text-sm leading-relaxed">${escHtml(detail)}</p>
1135
+ <div class="mt-3 flex items-center gap-2">
1136
+ <div class="h-1 flex-grow bg-surface-container-highest rounded-full overflow-hidden">
1137
+ <div class="h-full ${barColor}" style="width:${score.toFixed(0)}%"></div>
1138
+ </div>
1139
+ <span class="font-data-mono text-data-mono ${textColor} text-xs">${score.toFixed(0)}%</span>
1140
+ </div>
1141
+ </div>
1142
+ </div>`;
1143
+ }).join('');
1144
+ }
1145
+
1146
+ // ── Metadata terminal ─────────────────────────────────────────────────────────
1147
+ function renderMetaTerminal(data) {
1148
+ const meta = data.metadata || {};
1149
+ const rows = [
1150
+ ['RESULT', data.result || '--'],
1151
+ ['FRAMES_ANALYZED', meta.frames_analyzed ?? '--'],
1152
+ ['FACES_DETECTED', meta.frames_with_faces ?? '--'],
1153
+ ['DURATION', meta.video_duration_sec != null ? meta.video_duration_sec.toFixed(1) + 's' : '--'],
1154
+ ['FPS', meta.video_fps ?? '--'],
1155
+ ['RESOLUTION', meta.resolution || '--'],
1156
+ ['PROC_TIME', data.processing_time_sec != null ? data.processing_time_sec.toFixed(2) + 's' : '--'],
1157
+ ];
1158
+
1159
+ const html = rows.map(([k, v]) => `
1160
+ <div class="flex">
1161
+ <span class="text-outline w-36 shrink-0">${k}:</span>
1162
+ <span class="text-on-surface">${escHtml(String(v))}</span>
1163
+ </div>`).join('') + `
1164
+ <div class="my-3 border-t border-surface-variant/30 border-dashed"></div>
1165
+ <div class="text-primary-container opacity-80">&gt; INIT NEURAL_NET_V4.2</div>
1166
+ <div class="text-primary-container opacity-80">&gt; LOADING WEIGHTS... DONE</div>
1167
+ <div class="text-primary-container opacity-80">&gt; ANALYZING SPATIAL_TEMPORAL_NOISE</div>
1168
+ <div class="text-primary-container opacity-80">&gt; EXTRACTING BIOMETRIC_FEATURES</div>
1169
+ <div class="text-primary-container opacity-80">&gt; CROSS_REFERENCING KNOWN_GAN_MODELS</div>
1170
+ <div class="text-on-surface mt-3 blinking-cursor">&gt; ANALYSIS_COMPLETE</div>`;
1171
+
1172
+ document.getElementById('metaTerminal').innerHTML = html;
1173
+ }
1174
+
1175
+ // ── Timeline ──────────────────────────────────────────────────────────────────
1176
+ function renderTimeline(frames) {
1177
+ const container = document.getElementById('timelineBars');
1178
+ const labels = document.getElementById('timelineLabels');
1179
+
1180
+ if (!frames || !frames.length) {
1181
+ container.innerHTML = '<div class="flex-1 flex items-center justify-center text-on-surface-variant font-data-mono text-data-mono text-xs">No timeline data available</div>';
1182
+ labels.innerHTML = '';
1183
+ return;
1184
+ }
1185
+
1186
+ // Limit to 60 bars max for readability
1187
+ const step = Math.max(1, Math.floor(frames.length / 60));
1188
+ const subset = frames.filter((_, i) => i % step === 0);
1189
+ const maxH = 140; // px
1190
+
1191
+ container.innerHTML = subset.map(f => {
1192
+ const pct = typeof f.fake_pct === 'number' ? f.fake_pct : 0;
1193
+ const heightPct = Math.max(1.5, pct);
1194
+ const isHigh = pct > 60;
1195
+ const barBg = isHigh ? '#ffb4ab' : '#00ff9c';
1196
+ const barGlow = isHigh
1197
+ ? 'box-shadow:0 0 8px rgba(255,180,171,0.5);'
1198
+ : 'box-shadow:0 0 6px rgba(0,255,156,0.3);';
1199
+ return `
1200
+ <div class="tl-bar" style="height:${maxH}px;position:relative;">
1201
+ <div class="tl-tooltip">F${f.frame}: ${pct.toFixed(1)}%</div>
1202
+ <div style="position:absolute;bottom:0;left:0;right:0;height:${heightPct}%;background:${barBg};${barGlow}border-radius:2px 2px 0 0;"></div>
1203
+ </div>`;
1204
+ }).join('');
1205
+
1206
+ // Labels
1207
+ const first = frames[0].frame;
1208
+ const last = frames[frames.length - 1].frame;
1209
+ const mid = frames[Math.floor(frames.length / 2)].frame;
1210
+ labels.innerHTML = `<span>Frame ${first}</span><span>Frame ${mid}</span><span>Frame ${last}</span>`;
1211
+ }
1212
+
1213
+ // ── Audio section ─────────────────────────────────────────────────────────────
1214
+ function renderAudio(audio) {
1215
+ const isAI = audio.result === 'AI_VOICE';
1216
+
1217
+ const icon = document.getElementById('audioIcon');
1218
+ icon.textContent = isAI ? 'smart_toy' : 'record_voice_over';
1219
+ icon.style.color = isAI ? '#ffb4ab' : '#00ff9c';
1220
+
1221
+ const verdict = document.getElementById('audioVerdict');
1222
+ verdict.textContent = audio.result || '--';
1223
+ verdict.style.color = isAI ? '#ffb4ab' : '#00ff9c';
1224
+
1225
+ document.getElementById('audioConfidence').textContent =
1226
+ `Confidence: ${typeof audio.confidence === 'number' ? audio.confidence.toFixed(1) + '%' : '--'}`;
1227
+
1228
+ const modelScore = typeof audio.model_score === 'number' ? audio.model_score : 0;
1229
+ const heurScore = typeof audio.heuristic_score === 'number' ? audio.heuristic_score : 0;
1230
+
1231
+ document.getElementById('audioModelBar').style.width = modelScore + '%';
1232
+ document.getElementById('audioModelScore').textContent = modelScore.toFixed(1) + '%';
1233
+ document.getElementById('audioHeuristicBar').style.width = heurScore + '%';
1234
+ document.getElementById('audioHeuristicScore').textContent = heurScore.toFixed(1) + '%';
1235
+
1236
+ // Audio details
1237
+ const detailsEl = document.getElementById('audioDetails');
1238
+ if (audio.details && audio.details.length) {
1239
+ detailsEl.innerHTML = audio.details.map(d => `
1240
+ <div class="flex items-start gap-3 p-3 bg-surface-container-low/50 border border-outline-variant/30 rounded-DEFAULT">
1241
+ <span class="material-symbols-outlined text-on-surface-variant text-[16px] mt-0.5">chevron_right</span>
1242
+ <span class="font-data-mono text-data-mono text-on-surface-variant text-sm">${escHtml(d)}</span>
1243
+ </div>`).join('');
1244
+ } else {
1245
+ detailsEl.innerHTML = '';
1246
+ }
1247
+ }
1248
+
1249
+ // ── Reset ─────────────────────────────────────────────────────────────────────
1250
+ function resetAll() {
1251
+ selectedFile = null;
1252
+ document.getElementById('fileInput').value = '';
1253
+ document.getElementById('uploadPrompt').style.display = '';
1254
+ document.getElementById('fileChosen').style.display = 'none';
1255
+
1256
+ const btn = document.getElementById('analyzeBtn');
1257
+ btn.disabled = true;
1258
+ btn.style.pointerEvents = 'none';
1259
+ btn.className = 'w-full py-4 font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT transition-all duration-300 border border-outline/30 bg-surface-container/40 text-outline cursor-not-allowed';
1260
+
1261
+ document.getElementById('queueList').innerHTML = `
1262
+ <div class="bg-surface-container-low border border-outline-variant/30 rounded-DEFAULT p-4 flex items-center gap-3 opacity-40">
1263
+ <span class="material-symbols-outlined text-outline text-[20px]">inbox</span>
1264
+ <span class="font-data-mono text-data-mono text-on-surface-variant">No payload queued. Awaiting upload.</span>
1265
+ </div>`;
1266
+
1267
+ showState('heroSection');
1268
+ }
1269
+
1270
+ // ── Utilities ─────────────────────────────────────────────────────────────────
1271
+ function formatBytes(bytes) {
1272
+ if (bytes < 1024) return bytes + ' B';
1273
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
1274
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
1275
+ }
1276
+
1277
+ function escHtml(str) {
1278
+ return String(str)
1279
+ .replace(/&/g, '&amp;')
1280
+ .replace(/</g, '&lt;')
1281
+ .replace(/>/g, '&gt;')
1282
+ .replace(/"/g, '&quot;');
1283
+ }
1284
  </script>
 
1285
  </body>
1286
+ </html>