Antigravity AI commited on
Commit
8ceb96d
·
1 Parent(s): f178ad2

UI improvements and HUD refinements for deployment

Browse files
frontend/app/FaceVisionApp.tsx CHANGED
@@ -58,69 +58,75 @@ export default function FaceVisionApp() {
58
  return (
59
  <div className="min-h-screen animated-bg flex flex-col">
60
  {/* Header */}
61
- <header className="glass border-b border-white/5 px-6 py-4 flex items-center justify-between">
62
- <div className="flex items-center gap-3">
63
- <div className="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center text-lg shadow-lg">
64
- 🧠
 
 
 
65
  </div>
66
  <div>
67
  <h1
68
- className="text-lg font-bold gradient-text leading-none"
69
  style={{ fontFamily: "Outfit, sans-serif" }}
70
  >
71
  FaceVision AI
72
  </h1>
73
- <p className="text-xs text-white/30 mt-0.5">Real-Time Expression Recognition</p>
 
 
 
74
  </div>
75
  </div>
76
 
77
- <div className="flex items-center gap-3">
78
  {/* Model loading status */}
79
- <div className="flex items-center gap-2 glass rounded-full px-3 py-1.5">
80
  {loadError ? (
81
  <>
82
- <div className="w-2 h-2 rounded-full bg-red-400" />
83
- <span className="text-xs text-red-400">Model Error</span>
84
  </>
85
  ) : isLoaded ? (
86
  <>
87
- <div className="w-2 h-2 rounded-full bg-green-400" />
88
- <span className="text-xs text-green-400">Model Ready</span>
89
  </>
90
  ) : (
91
  <>
92
- <div className="w-2 h-2 rounded-full bg-yellow-400 animate-blink" />
93
- <span className="text-xs text-yellow-400">Loading Model…</span>
94
  </>
95
  )}
96
  </div>
97
 
98
  {/* Backend status */}
99
  {logError ? (
100
- <div className="flex items-center gap-2 glass rounded-full px-3 py-1.5">
101
- <div className="w-2 h-2 rounded-full bg-orange-400" />
102
- <span className="text-xs text-orange-400">Backend offline</span>
103
  </div>
104
  ) : lastLog ? (
105
- <div className="flex items-center gap-2 glass rounded-full px-3 py-1.5">
106
- <div className="w-2 h-2 rounded-full bg-green-400" />
107
- <span className="text-xs text-green-400">Logged</span>
108
  </div>
109
  ) : null}
110
  </div>
111
  </header>
112
 
113
  {/* Main layout */}
114
- <main className="flex-1 flex flex-col lg:flex-row gap-5 p-5 max-w-7xl mx-auto w-full">
115
  {/* Left sidebar */}
116
- <aside className="w-full lg:w-72 flex flex-col gap-4 order-2 lg:order-1">
117
  {/* Emotion display */}
118
- <div className="glass rounded-2xl p-5 space-y-3">
119
  <h2
120
- className="text-xs font-semibold text-white/50 uppercase tracking-widest"
121
  style={{ fontFamily: "Outfit, sans-serif" }}
122
  >
123
- Current Expression
124
  </h2>
125
  <EmotionDisplay emotion={currentEmotion} faceDetected={stats.faceDetected} />
126
  </div>
@@ -134,32 +140,49 @@ export default function FaceVisionApp() {
134
 
135
  {/* Last log entry */}
136
  {lastLog && (
137
- <div className="glass rounded-2xl p-4 space-y-2 animate-fade-in-up">
138
- <p className="text-xs text-white/40 uppercase tracking-wider">Last Log</p>
139
- <p className="text-sm text-white/70">
140
- <span className="font-medium text-white">{lastLog.dominant_emotion}</span>
141
- {" "}for {lastLog.duration_seconds}s
142
- </p>
143
- <p className="text-xs text-white/30">
144
- {new Date(lastLog.timestamp).toLocaleTimeString()}
145
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
146
  </div>
147
  )}
148
  </aside>
149
 
150
  {/* Video Feed */}
151
- <section className="flex-1 flex flex-col gap-4 order-1 lg:order-2">
152
- <div className="relative flex-1 rounded-3xl overflow-hidden glass min-h-[400px]">
153
  {/* Camera error state */}
154
  {cameraError ? (
155
- <div className="absolute inset-0 flex flex-col items-center justify-center gap-4 p-8 text-center">
156
- <div className="text-5xl">🚫</div>
157
- <p className="text-white/60 text-sm max-w-sm">{cameraError}</p>
 
 
 
 
 
158
  <button
159
  onClick={() => window.location.reload()}
160
- className="px-5 py-2.5 rounded-xl glass-strong text-sm text-white/70 hover:text-white transition-colors border border-white/10 hover:border-white/20"
161
  >
162
- Refresh Page
163
  </button>
164
  </div>
165
  ) : (
@@ -172,113 +195,150 @@ export default function FaceVisionApp() {
172
  onUserMedia={handleUserMedia}
173
  onUserMediaError={handleUserMediaError}
174
  videoConstraints={{
175
- width: { ideal: 1280 },
176
- height: { ideal: 720 },
177
  facingMode: "user",
178
- frameRate: { ideal: 30, max: 60 },
179
  }}
180
- className="w-full h-full object-cover"
181
  style={{ display: isCameraReady ? "block" : "none" }}
182
  />
183
 
184
  {/* Loading skeleton */}
185
  {!isCameraReady && (
186
- <div className="absolute inset-0 flex flex-col items-center justify-center gap-4">
187
- <div className="w-16 h-16 rounded-2xl border-2 border-blue-400/30 border-t-blue-400 animate-rotate" />
188
- <p className="text-sm text-white/40">Requesting camera access…</p>
 
 
 
 
 
 
189
  </div>
190
  )}
191
 
192
- {/* Emotion overlay glow border */}
193
- {isCameraReady && isTracking && (
194
- <div
195
- className="absolute inset-0 pointer-events-none rounded-3xl transition-all duration-500"
196
- style={{
197
- boxShadow: `inset 0 0 60px ${glowColor}20, 0 0 40px ${glowColor}15`,
198
- border: `1px solid ${glowColor}30`,
199
- }}
200
- />
201
- )}
202
-
203
- {/* Corner brackets overlay */}
204
  {isCameraReady && isTracking && (
205
  <>
206
- <div className="absolute top-4 left-4 w-8 h-8 border-t-2 border-l-2 border-blue-400/50 rounded-tl-lg" />
207
- <div className="absolute top-4 right-4 w-8 h-8 border-t-2 border-r-2 border-blue-400/50 rounded-tr-lg" />
208
- <div className="absolute bottom-4 left-4 w-8 h-8 border-b-2 border-l-2 border-blue-400/50 rounded-bl-lg" />
209
- <div className="absolute bottom-4 right-4 w-8 h-8 border-b-2 border-r-2 border-blue-400/50 rounded-br-lg" />
210
- </>
211
- )}
212
-
213
- {/* Emotion badge overlay */}
214
- {isCameraReady && isTracking && stats.faceDetected && (
215
- <div
216
- key={currentEmotion}
217
- className="absolute top-5 left-1/2 -translate-x-1/2 animate-emotion"
218
- >
219
  <div
220
- className="px-5 py-2 rounded-full glass-strong backdrop-blur-xl border text-sm font-semibold transition-all duration-300"
221
  style={{
222
- borderColor: `${glowColor}50`,
223
- color: glowColor,
224
- boxShadow: `0 0 20px ${glowColor}20`,
225
  }}
226
- >
227
- {currentEmotion === "Happy" && "😊 "}
228
- {currentEmotion === "Sad" && "😢 "}
229
- {currentEmotion === "Angry" && "😠 "}
230
- {currentEmotion === "Surprised" && "😲 "}
231
- {currentEmotion === "Neutral" && "😐 "}
232
- {currentEmotion}
 
233
  </div>
234
- </div>
235
- )}
236
 
237
- {/* Scan line when tracking */}
238
- {isCameraReady && isTracking && (
239
- <div className="absolute inset-0 overflow-hidden pointer-events-none rounded-3xl">
240
- <div className="w-full h-0.5 bg-gradient-to-r from-transparent via-blue-400/40 to-transparent absolute"
241
- style={{ animation: "scan-line 4s linear infinite" }} />
242
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  )}
244
  </>
245
  )}
246
  </div>
247
 
248
  {/* Controls */}
249
- <div className="glass rounded-2xl p-4 flex items-center justify-between gap-4">
250
- <div className="text-sm text-white/40">
251
- {!isCameraReady
252
- ? "Waiting for camera…"
253
- : !isLoaded
254
- ? "Loading AI model…"
255
- : isTracking
256
- ? `Tracking active Logs every 10s`
257
- : "Ready to track"}
 
 
 
 
 
 
 
258
  </div>
259
 
260
  <button
261
  id="tracking-toggle-btn"
262
  onClick={() => setIsTracking((v) => !v)}
263
  disabled={!isCameraReady || !isLoaded || !!loadError}
264
- className={`px-6 py-2.5 rounded-xl text-sm font-semibold transition-all duration-200 disabled:opacity-30 disabled:cursor-not-allowed ${
265
  isTracking
266
- ? "bg-red-500/20 border border-red-500/40 text-red-400 hover:bg-red-500/30"
267
- : "bg-gradient-to-r from-blue-500 to-purple-600 text-white hover:opacity-90 shadow-lg"
268
  }`}
269
  >
270
- {isTracking ? " Stop Tracking" : "▶ Start Tracking"}
 
 
 
 
 
 
 
 
 
 
 
 
271
  </button>
272
  </div>
273
  </section>
274
  </main>
275
 
276
  {/* Footer */}
277
- <footer className="glass border-t border-white/5 py-3 px-6 text-center">
278
- <p className="text-xs text-white/20">
279
- Powered by MediaPipe Face Landmarker · Processing runs 100% in-browser · No video is uploaded
280
  </p>
 
 
 
 
281
  </footer>
 
282
  </div>
283
  );
284
  }
 
58
  return (
59
  <div className="min-h-screen animated-bg flex flex-col">
60
  {/* Header */}
61
+ <header className="glass border-b border-white/5 px-8 py-5 flex items-center justify-between sticky top-0 z-50">
62
+ <div className="flex items-center gap-4">
63
+ <div className="relative group">
64
+ <div className="absolute inset-0 bg-blue-500 blur-lg opacity-20 group-hover:opacity-40 transition-opacity" />
65
+ <div className="relative w-11 h-11 rounded-2xl bg-gradient-to-br from-blue-500 via-indigo-500 to-purple-600 flex items-center justify-center text-xl shadow-2xl transform group-hover:scale-105 transition-transform duration-300">
66
+ 🧠
67
+ </div>
68
  </div>
69
  <div>
70
  <h1
71
+ className="text-xl font-black gradient-text tracking-tighter"
72
  style={{ fontFamily: "Outfit, sans-serif" }}
73
  >
74
  FaceVision AI
75
  </h1>
76
+ <div className="flex items-center gap-2">
77
+ <span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" />
78
+ <p className="text-[10px] text-white/30 font-bold uppercase tracking-[0.2em]">Neural Engine v1.0</p>
79
+ </div>
80
  </div>
81
  </div>
82
 
83
+ <div className="flex items-center gap-4">
84
  {/* Model loading status */}
85
+ <div className="flex items-center gap-2.5 glass-strong rounded-xl px-4 py-2 border border-white/5">
86
  {loadError ? (
87
  <>
88
+ <div className="w-1.5 h-1.5 rounded-full bg-red-400 shadow-[0_0_8px_rgba(248,113,113,0.6)]" />
89
+ <span className="text-[10px] font-bold text-red-400 uppercase tracking-widest">Engine Error</span>
90
  </>
91
  ) : isLoaded ? (
92
  <>
93
+ <div className="w-1.5 h-1.5 rounded-full bg-green-400 shadow-[0_0_8px_rgba(74,222,128,0.6)]" />
94
+ <span className="text-[10px] font-bold text-green-400 uppercase tracking-widest">Engine Ready</span>
95
  </>
96
  ) : (
97
  <>
98
+ <div className="w-1.5 h-1.5 rounded-full bg-yellow-400 animate-blink shadow-[0_0_8px_rgba(250,204,21,0.6)]" />
99
+ <span className="text-[10px] font-bold text-yellow-400 uppercase tracking-widest">Initializing…</span>
100
  </>
101
  )}
102
  </div>
103
 
104
  {/* Backend status */}
105
  {logError ? (
106
+ <div className="flex items-center gap-2.5 glass-strong rounded-xl px-4 py-2 border border-white/5">
107
+ <div className="w-1.5 h-1.5 rounded-full bg-orange-400/50" />
108
+ <span className="text-[10px] font-bold text-orange-400 uppercase tracking-widest">Offline</span>
109
  </div>
110
  ) : lastLog ? (
111
+ <div className="flex items-center gap-2.5 glass-strong rounded-xl px-4 py-2 border border-white/5 animate-pulse-glow">
112
+ <div className="w-1.5 h-1.5 rounded-full bg-blue-400 shadow-[0_0_8px_rgba(96,165,250,0.6)]" />
113
+ <span className="text-[10px] font-bold text-blue-400 uppercase tracking-widest">Syncing</span>
114
  </div>
115
  ) : null}
116
  </div>
117
  </header>
118
 
119
  {/* Main layout */}
120
+ <main className="flex-1 flex flex-col lg:flex-row gap-8 p-8 max-w-[1600px] mx-auto w-full">
121
  {/* Left sidebar */}
122
+ <aside className="w-full lg:w-80 flex flex-col gap-6 order-2 lg:order-1 animate-slide-in">
123
  {/* Emotion display */}
124
+ <div className="space-y-3">
125
  <h2
126
+ className="px-1 text-[10px] font-bold text-white/30 uppercase tracking-[0.3em]"
127
  style={{ fontFamily: "Outfit, sans-serif" }}
128
  >
129
+ Current State
130
  </h2>
131
  <EmotionDisplay emotion={currentEmotion} faceDetected={stats.faceDetected} />
132
  </div>
 
140
 
141
  {/* Last log entry */}
142
  {lastLog && (
143
+ <div className="glass rounded-2xl p-5 space-y-3 border-l-2 border-l-blue-500/50 relative overflow-hidden group hover:bg-white/[0.05] transition-colors">
144
+ <div className="absolute top-0 right-0 p-2 opacity-10 group-hover:opacity-20 transition-opacity">
145
+ <svg className="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
146
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
147
+ </svg>
148
+ </div>
149
+ <p className="text-[10px] text-blue-400 font-bold uppercase tracking-widest">Recent Activity</p>
150
+ <div>
151
+ <p className="text-lg font-bold text-white leading-tight">
152
+ {lastLog.dominant_emotion}
153
+ </p>
154
+ <p className="text-xs text-white/40 mt-1">
155
+ Sustained for {lastLog.duration_seconds}s
156
+ </p>
157
+ </div>
158
+ <div className="pt-2 flex items-center justify-between border-t border-white/5">
159
+ <span className="text-[10px] text-white/20 font-mono">
160
+ {new Date(lastLog.timestamp).toLocaleTimeString()}
161
+ </span>
162
+ <span className="text-[10px] text-green-500 font-bold uppercase tracking-tighter">Verified</span>
163
+ </div>
164
  </div>
165
  )}
166
  </aside>
167
 
168
  {/* Video Feed */}
169
+ <section className="flex-1 flex flex-col gap-6 order-1 lg:order-2 animate-scale-up">
170
+ <div className="relative flex-1 rounded-[2.5rem] overflow-hidden glass min-h-[500px] border border-white/10 shadow-2xl">
171
  {/* Camera error state */}
172
  {cameraError ? (
173
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-6 p-12 text-center bg-black/40 backdrop-blur-md">
174
+ <div className="w-20 h-20 rounded-3xl bg-red-500/10 border border-red-500/20 flex items-center justify-center text-4xl">
175
+ ⚠️
176
+ </div>
177
+ <div>
178
+ <h3 className="text-xl font-bold text-white mb-2">Camera Access Required</h3>
179
+ <p className="text-white/40 text-sm max-w-xs mx-auto leading-relaxed">{cameraError}</p>
180
+ </div>
181
  <button
182
  onClick={() => window.location.reload()}
183
+ className="px-8 py-3 rounded-2xl bg-white/5 hover:bg-white/10 text-sm font-bold text-white transition-all border border-white/10 hover:border-white/20"
184
  >
185
+ Request Again
186
  </button>
187
  </div>
188
  ) : (
 
195
  onUserMedia={handleUserMedia}
196
  onUserMediaError={handleUserMediaError}
197
  videoConstraints={{
198
+ width: { ideal: 1920 },
199
+ height: { ideal: 1080 },
200
  facingMode: "user",
201
+ frameRate: { ideal: 60 },
202
  }}
203
+ className="w-full h-full object-cover scale-[1.01]"
204
  style={{ display: isCameraReady ? "block" : "none" }}
205
  />
206
 
207
  {/* Loading skeleton */}
208
  {!isCameraReady && (
209
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-6 bg-[#070b14]">
210
+ <div className="relative">
211
+ <div className="w-20 h-20 rounded-[2rem] border-2 border-blue-500/20" />
212
+ <div className="absolute inset-0 w-20 h-20 rounded-[2rem] border-t-2 border-blue-500 animate-spin" />
213
+ </div>
214
+ <div className="text-center">
215
+ <p className="text-sm font-bold text-white/60 tracking-widest uppercase">Initializing Vision</p>
216
+ <p className="text-xs text-white/20 mt-2">Connecting to hardware layer…</p>
217
+ </div>
218
  </div>
219
  )}
220
 
221
+ {/* HUD Elements */}
 
 
 
 
 
 
 
 
 
 
 
222
  {isCameraReady && isTracking && (
223
  <>
224
+ {/* Emotion overlay glow border */}
 
 
 
 
 
 
 
 
 
 
 
 
225
  <div
226
+ className="absolute inset-0 pointer-events-none transition-all duration-700"
227
  style={{
228
+ boxShadow: `inset 0 0 100px ${glowColor}15, inset 0 0 40px ${glowColor}10`,
229
+ border: `2px solid ${glowColor}20`,
 
230
  }}
231
+ />
232
+
233
+ {/* Corner brackets overlay */}
234
+ <div className="absolute inset-8 pointer-events-none opacity-40">
235
+ <div className="absolute top-0 left-0 w-12 h-12 border-t-2 border-l-2 border-blue-400 rounded-tl-2xl" />
236
+ <div className="absolute top-0 right-0 w-12 h-12 border-t-2 border-r-2 border-blue-400 rounded-tr-2xl" />
237
+ <div className="absolute bottom-0 left-0 w-12 h-12 border-b-2 border-l-2 border-blue-400 rounded-bl-2xl" />
238
+ <div className="absolute bottom-0 right-0 w-12 h-12 border-b-2 border-r-2 border-blue-400 rounded-br-2xl" />
239
  </div>
 
 
240
 
241
+ {/* Emotion badge overlay */}
242
+ {stats.faceDetected && (
243
+ <div
244
+ key={currentEmotion}
245
+ className="absolute bottom-10 left-1/2 -translate-x-1/2 animate-emotion"
246
+ >
247
+ <div
248
+ className="px-8 py-3 rounded-2xl glass-strong border-2 backdrop-blur-3xl text-lg font-black transition-all duration-500 flex items-center gap-3 shadow-[0_20px_50px_rgba(0,0,0,0.5)]"
249
+ style={{
250
+ borderColor: `${glowColor}30`,
251
+ color: glowColor,
252
+ boxShadow: `0 0 30px ${glowColor}15`,
253
+ }}
254
+ >
255
+ <span className="text-2xl filter drop-shadow-md">
256
+ {currentEmotion === "Happy" && "😊"}
257
+ {currentEmotion === "Sad" && "😢"}
258
+ {currentEmotion === "Angry" && "😠"}
259
+ {currentEmotion === "Surprised" && "😲"}
260
+ {currentEmotion === "Neutral" && "😐"}
261
+ </span>
262
+ <span className="tracking-tighter uppercase">{currentEmotion}</span>
263
+ </div>
264
+ </div>
265
+ )}
266
+
267
+ {/* Scanning Grid Effect */}
268
+ <div className="absolute inset-0 overflow-hidden pointer-events-none opacity-20">
269
+ <div className="w-full h-full"
270
+ style={{
271
+ backgroundImage: 'radial-gradient(circle, white 1px, transparent 1px)',
272
+ backgroundSize: '40px 40px'
273
+ }}
274
+ />
275
+ <div className="w-full h-1 bg-gradient-to-r from-transparent via-blue-400 to-transparent absolute shadow-[0_0_20px_rgba(96,165,250,0.5)]"
276
+ style={{ animation: "scan-line 6s ease-in-out infinite" }} />
277
+ </div>
278
+ </>
279
  )}
280
  </>
281
  )}
282
  </div>
283
 
284
  {/* Controls */}
285
+ <div className="glass rounded-3xl p-5 flex items-center justify-between gap-6 border border-white/5 shadow-xl">
286
+ <div className="flex items-center gap-4">
287
+ <div className={`flex items-center gap-2 px-3 py-1.5 rounded-xl bg-white/5 border border-white/5 transition-all duration-500 ${isTracking ? 'opacity-100' : 'opacity-40'}`}>
288
+ <div className={`w-2 h-2 rounded-full ${isTracking ? 'bg-blue-400 animate-pulse shadow-[0_0_8px_rgba(96,165,250,0.6)]' : 'bg-white/20'}`} />
289
+ <span className="text-[10px] font-bold text-white/50 uppercase tracking-widest">System Live</span>
290
+ </div>
291
+ <div className="h-4 w-px bg-white/10" />
292
+ <p className="text-xs text-white/30 font-medium tracking-tight">
293
+ {!isCameraReady
294
+ ? "Waiting for sensor input…"
295
+ : !isLoaded
296
+ ? "Loading neural weights…"
297
+ : isTracking
298
+ ? `Processing window active • Log interval 10s`
299
+ : "Vision system standby"}
300
+ </p>
301
  </div>
302
 
303
  <button
304
  id="tracking-toggle-btn"
305
  onClick={() => setIsTracking((v) => !v)}
306
  disabled={!isCameraReady || !isLoaded || !!loadError}
307
+ className={`relative group px-10 py-4 rounded-2xl text-sm font-black transition-all duration-300 disabled:opacity-20 disabled:cursor-not-allowed overflow-hidden ${
308
  isTracking
309
+ ? "bg-red-500/10 text-red-400 border border-red-500/30 hover:bg-red-500/20"
310
+ : "bg-white text-black hover:bg-white/90 shadow-[0_10px_20px_rgba(255,255,255,0.1)]"
311
  }`}
312
  >
313
+ <div className="relative z-10 flex items-center gap-2">
314
+ {isTracking ? (
315
+ <>
316
+ <span className="text-lg">⏹</span>
317
+ <span className="uppercase tracking-tighter">Disable</span>
318
+ </>
319
+ ) : (
320
+ <>
321
+ <span className="text-lg">▶</span>
322
+ <span className="uppercase tracking-tighter">Initialize</span>
323
+ </>
324
+ )}
325
+ </div>
326
  </button>
327
  </div>
328
  </section>
329
  </main>
330
 
331
  {/* Footer */}
332
+ <footer className="glass border-t border-white/5 py-4 px-8 flex items-center justify-between">
333
+ <p className="text-[10px] text-white/20 font-bold uppercase tracking-[0.2em]">
334
+ End-to-End Encryption Local Edge Inference
335
  </p>
336
+ <div className="flex items-center gap-6">
337
+ <span className="text-[10px] text-white/20 font-bold uppercase tracking-[0.2em]">MediaPipe AI</span>
338
+ <span className="text-[10px] text-white/20 font-bold uppercase tracking-[0.2em]">WASM Optimized</span>
339
+ </div>
340
  </footer>
341
+
342
  </div>
343
  );
344
  }
frontend/app/components/EmotionDisplay.tsx CHANGED
@@ -53,26 +53,59 @@ export default function EmotionDisplay({ emotion, faceDetected }: EmotionDisplay
53
 
54
  if (!faceDetected) {
55
  return (
56
- <div className="glass rounded-2xl p-5 flex items-center gap-4 border border-dashed border-white/10">
57
- <div className="text-3xl opacity-40">👤</div>
 
 
 
 
 
58
  <div>
59
- <p className="text-sm font-medium text-white/30">No Face Detected</p>
60
- <p className="text-xs text-white/20 mt-0.5">Position your face in the camera</p>
61
  </div>
62
  </div>
63
  );
64
  }
65
 
 
 
 
 
 
 
 
 
66
  return (
67
  <div
68
  key={emotion}
69
- className={`glass rounded-2xl p-5 flex items-center gap-4 border animate-emotion ${config.bgClass}`}
 
 
 
70
  >
71
- <div className="text-4xl">{config.emoji}</div>
72
- <div>
73
- <p className={`text-xl font-bold ${config.colorClass}`}>{config.label}</p>
74
- <p className="text-xs text-white/40 mt-0.5">{config.description}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  </div>
76
  </div>
77
  );
78
  }
 
 
53
 
54
  if (!faceDetected) {
55
  return (
56
+ <div className="glass rounded-2xl p-6 flex items-center gap-5 border border-dashed border-white/10 animate-pulse-slow">
57
+ <div className="relative">
58
+ <div className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center text-2xl grayscale opacity-30">
59
+ 👤
60
+ </div>
61
+ <div className="absolute inset-0 rounded-full border border-white/10 animate-ping opacity-20" />
62
+ </div>
63
  <div>
64
+ <p className="text-sm font-bold text-white/40 tracking-tight uppercase">Ready to Scan</p>
65
+ <p className="text-[11px] text-white/20 mt-0.5 leading-tight">Please position your face<br/>clearly in the camera</p>
66
  </div>
67
  </div>
68
  );
69
  }
70
 
71
+ const emotionColors: Record<EmotionType, string> = {
72
+ Happy: "rgba(104, 211, 145, 0.4)",
73
+ Sad: "rgba(118, 228, 247, 0.4)",
74
+ Angry: "rgba(252, 129, 129, 0.4)",
75
+ Surprised: "rgba(246, 173, 85, 0.4)",
76
+ Neutral: "rgba(160, 174, 192, 0.3)",
77
+ };
78
+
79
  return (
80
  <div
81
  key={emotion}
82
+ className={`relative glass rounded-2xl p-5 flex items-center gap-5 border overflow-hidden transition-all duration-500 ${config.bgClass} animate-emotion`}
83
+ style={{
84
+ boxShadow: `0 10px 40px -10px ${emotionColors[emotion]}`,
85
+ }}
86
  >
87
+ {/* Background Glow */}
88
+ <div
89
+ className="absolute -right-4 -top-4 w-24 h-24 blur-3xl rounded-full opacity-20 transition-all duration-700"
90
+ style={{ background: emotionColors[emotion] }}
91
+ />
92
+
93
+ <div className="relative text-5xl animate-float filter drop-shadow-lg">
94
+ {config.emoji}
95
+ </div>
96
+
97
+ <div className="relative">
98
+ <div className="flex items-center gap-2 mb-0.5">
99
+ <p className={`text-2xl font-black tracking-tight ${config.colorClass}`}>
100
+ {config.label}
101
+ </p>
102
+ <div className={`w-1.5 h-1.5 rounded-full animate-blink ${config.bgClass}`} style={{ background: 'currentColor' }} />
103
+ </div>
104
+ <p className="text-xs text-white/40 font-medium">
105
+ {config.description}
106
+ </p>
107
  </div>
108
  </div>
109
  );
110
  }
111
+
frontend/app/components/StatsPanel.tsx CHANGED
@@ -8,20 +8,23 @@ interface BlendshapeBarProps {
8
 
9
  function BlendshapeBar({ name, value, color = "#63b3ed" }: BlendshapeBarProps) {
10
  return (
11
- <div className="flex items-center gap-2">
12
- <span className="text-xs text-white/40 w-28 truncate flex-shrink-0">{name}</span>
13
- <div className="flex-1 h-1.5 bg-white/5 rounded-full overflow-hidden">
 
 
 
 
 
14
  <div
15
- className="h-full rounded-full transition-all duration-100"
16
  style={{
17
  width: `${Math.round(value * 100)}%`,
18
- background: color,
 
19
  }}
20
  />
21
  </div>
22
- <span className="text-xs text-white/30 w-8 text-right flex-shrink-0">
23
- {Math.round(value * 100)}
24
- </span>
25
  </div>
26
  );
27
  }
@@ -36,7 +39,7 @@ const KEY_BLENDSHAPES: Array<{ key: string; label: string; color: string }> = [
36
  { key: "mouthSmileLeft", label: "Smile L", color: "#68d391" },
37
  { key: "mouthSmileRight", label: "Smile R", color: "#68d391" },
38
  { key: "browInnerUp", label: "Brow Up", color: "#f6ad55" },
39
- { key: "jawOpen", label: "Jaw Open", color: "#f6ad55" },
40
  { key: "browDownLeft", label: "Brow ↓L", color: "#fc8181" },
41
  { key: "browDownRight", label: "Brow ↓R", color: "#fc8181" },
42
  { key: "mouthFrownLeft", label: "Frown L", color: "#76e4f7" },
@@ -45,52 +48,71 @@ const KEY_BLENDSHAPES: Array<{ key: string; label: string; color: string }> = [
45
 
46
  export default function StatsPanel({ fps, faceDetected, blendshapes }: StatsPanel) {
47
  return (
48
- <div className="glass rounded-2xl p-5 space-y-4">
49
  <div className="flex items-center justify-between">
50
- <h3
51
- style={{ fontFamily: "Outfit, sans-serif" }}
52
- className="text-sm font-semibold text-white/60 uppercase tracking-widest"
53
- >
54
- Analytics
55
- </h3>
56
  <div className="flex items-center gap-2">
57
- <div
58
- className={`w-2 h-2 rounded-full ${faceDetected ? "bg-green-400 animate-pulse" : "bg-red-400/50"}`}
59
- />
60
- <span className="text-xs text-white/40">{faceDetected ? "Tracking" : "Searching"}</span>
 
 
 
 
 
 
 
 
 
 
 
 
61
  </div>
62
  </div>
63
 
64
- {/* FPS Badge */}
65
- <div className="flex items-center gap-3">
66
- <div className="glass-strong rounded-xl px-4 py-2 flex-1 text-center">
67
- <p className="text-2xl font-bold gradient-text" style={{ fontFamily: "Outfit, sans-serif" }}>
68
- {fps}
69
- </p>
70
- <p className="text-xs text-white/30">FPS</p>
 
 
 
71
  </div>
72
- <div className="glass-strong rounded-xl px-4 py-2 flex-1 text-center">
73
- <p className="text-2xl font-bold text-white/80" style={{ fontFamily: "Outfit, sans-serif" }}>
74
- {faceDetected ? "1" : "0"}
75
- </p>
76
- <p className="text-xs text-white/30">Faces</p>
 
 
 
77
  </div>
78
  </div>
79
 
80
  {/* Blendshapes */}
81
  {faceDetected && (
82
- <div className="space-y-2">
83
- <p className="text-xs text-white/30 uppercase tracking-wider">Blendshapes</p>
84
- {KEY_BLENDSHAPES.map(({ key, label, color }) => (
85
- <BlendshapeBar
86
- key={key}
87
- name={label}
88
- value={blendshapes[key] ?? 0}
89
- color={color}
90
- />
91
- ))}
 
 
 
 
 
 
92
  </div>
93
  )}
94
  </div>
95
  );
96
  }
 
 
8
 
9
  function BlendshapeBar({ name, value, color = "#63b3ed" }: BlendshapeBarProps) {
10
  return (
11
+ <div className="flex flex-col gap-1.5">
12
+ <div className="flex items-center justify-between px-0.5">
13
+ <span className="text-[10px] text-white/40 uppercase tracking-wider font-medium">{name}</span>
14
+ <span className="text-[10px] text-white/30 font-mono">
15
+ {Math.round(value * 100)}%
16
+ </span>
17
+ </div>
18
+ <div className="h-1.5 bg-white/5 rounded-full overflow-hidden border border-white/5 shadow-inner">
19
  <div
20
+ className="h-full rounded-full transition-all duration-300 ease-out"
21
  style={{
22
  width: `${Math.round(value * 100)}%`,
23
+ background: `linear-gradient(90deg, ${color}cc, ${color})`,
24
+ boxShadow: `0 0 10px ${color}40`,
25
  }}
26
  />
27
  </div>
 
 
 
28
  </div>
29
  );
30
  }
 
39
  { key: "mouthSmileLeft", label: "Smile L", color: "#68d391" },
40
  { key: "mouthSmileRight", label: "Smile R", color: "#68d391" },
41
  { key: "browInnerUp", label: "Brow Up", color: "#f6ad55" },
42
+ { key: "jawOpen", label: "Jaw Open", color: "#63b3ed" },
43
  { key: "browDownLeft", label: "Brow ↓L", color: "#fc8181" },
44
  { key: "browDownRight", label: "Brow ↓R", color: "#fc8181" },
45
  { key: "mouthFrownLeft", label: "Frown L", color: "#76e4f7" },
 
48
 
49
  export default function StatsPanel({ fps, faceDetected, blendshapes }: StatsPanel) {
50
  return (
51
+ <div className="glass rounded-2xl p-5 space-y-6 animate-scale-up">
52
  <div className="flex items-center justify-between">
 
 
 
 
 
 
53
  <div className="flex items-center gap-2">
54
+ <div className="p-1.5 rounded-lg bg-blue-500/10 border border-blue-500/20">
55
+ <svg className="w-3.5 h-3.5 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
56
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
57
+ </svg>
58
+ </div>
59
+ <h3
60
+ style={{ fontFamily: "Outfit, sans-serif" }}
61
+ className="text-xs font-bold text-white/70 uppercase tracking-widest"
62
+ >
63
+ Analytics
64
+ </h3>
65
+ </div>
66
+ <div className={`px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-tighter border transition-colors ${
67
+ faceDetected ? "bg-green-500/10 border-green-500/30 text-green-400" : "bg-red-500/10 border-red-500/30 text-red-400"
68
+ }`}>
69
+ {faceDetected ? "Active" : "Idle"}
70
  </div>
71
  </div>
72
 
73
+ {/* Stats Grid */}
74
+ <div className="grid grid-cols-2 gap-3">
75
+ <div className="glass-strong rounded-xl p-3 border border-white/5 group hover:border-white/10 transition-colors">
76
+ <p className="text-xs text-white/30 mb-1">Performance</p>
77
+ <div className="flex items-baseline gap-1">
78
+ <span className="text-2xl font-bold text-white tracking-tight" style={{ fontFamily: "Outfit, sans-serif" }}>
79
+ {fps}
80
+ </span>
81
+ <span className="text-[10px] text-white/20 font-bold uppercase">fps</span>
82
+ </div>
83
  </div>
84
+ <div className="glass-strong rounded-xl p-3 border border-white/5 group hover:border-white/10 transition-colors">
85
+ <p className="text-xs text-white/30 mb-1">Detection</p>
86
+ <div className="flex items-baseline gap-1">
87
+ <span className="text-2xl font-bold text-white tracking-tight" style={{ fontFamily: "Outfit, sans-serif" }}>
88
+ {faceDetected ? "1" : "0"}
89
+ </span>
90
+ <span className="text-[10px] text-white/20 font-bold uppercase">face</span>
91
+ </div>
92
  </div>
93
  </div>
94
 
95
  {/* Blendshapes */}
96
  {faceDetected && (
97
+ <div className="space-y-4 pt-2">
98
+ <div className="flex items-center gap-2">
99
+ <div className="h-px flex-1 bg-gradient-to-r from-transparent via-white/10 to-transparent" />
100
+ <span className="text-[10px] text-white/20 uppercase tracking-widest font-bold">Signals</span>
101
+ <div className="h-px flex-1 bg-gradient-to-r from-transparent via-white/10 to-transparent" />
102
+ </div>
103
+ <div className="grid grid-cols-1 gap-4">
104
+ {KEY_BLENDSHAPES.map(({ key, label, color }) => (
105
+ <BlendshapeBar
106
+ key={key}
107
+ name={label}
108
+ value={blendshapes[key] ?? 0}
109
+ color={color}
110
+ />
111
+ ))}
112
+ </div>
113
  </div>
114
  )}
115
  </div>
116
  );
117
  }
118
+
frontend/app/globals.css CHANGED
@@ -39,17 +39,19 @@ html, body {
39
 
40
  /* Glassmorphism */
41
  .glass {
42
- background: rgba(255, 255, 255, 0.04);
43
- backdrop-filter: blur(20px);
44
- -webkit-backdrop-filter: blur(20px);
45
  border: 1px solid rgba(255, 255, 255, 0.08);
 
46
  }
47
 
48
  .glass-strong {
49
- background: rgba(255, 255, 255, 0.07);
50
- backdrop-filter: blur(40px);
51
- -webkit-backdrop-filter: blur(40px);
52
  border: 1px solid rgba(255, 255, 255, 0.12);
 
53
  }
54
 
55
  /* Gradient text */
@@ -58,26 +60,44 @@ html, body {
58
  -webkit-background-clip: text;
59
  -webkit-text-fill-color: transparent;
60
  background-clip: text;
 
61
  }
62
 
63
- /* Animated gradient background */
64
- .animated-bg {
65
- background:
66
- radial-gradient(ellipse at 20% 50%, rgba(99, 179, 237, 0.08) 0%, transparent 50%),
67
- radial-gradient(ellipse at 80% 20%, rgba(159, 122, 234, 0.08) 0%, transparent 50%),
68
- radial-gradient(ellipse at 60% 80%, rgba(104, 211, 145, 0.05) 0%, transparent 50%),
69
- var(--bg-primary);
70
  }
71
 
72
- /* Pulse animation */
73
- @keyframes pulse-glow {
74
- 0%, 100% { box-shadow: 0 0 20px rgba(99, 179, 237, 0.2); }
75
- 50% { box-shadow: 0 0 40px rgba(99, 179, 237, 0.4), 0 0 80px rgba(99, 179, 237, 0.1); }
 
 
 
 
 
76
  }
77
 
78
- @keyframes float {
79
- 0%, 100% { transform: translateY(0px); }
80
- 50% { transform: translateY(-6px); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
82
 
83
  @keyframes scan-line {
@@ -90,11 +110,6 @@ html, body {
90
  100% { opacity: 1; transform: scale(1) translateY(0px); }
91
  }
92
 
93
- @keyframes shimmer {
94
- 0% { background-position: -200% center; }
95
- 100% { background-position: 200% center; }
96
- }
97
-
98
  @keyframes blink {
99
  0%, 100% { opacity: 1; }
100
  50% { opacity: 0.3; }
@@ -110,8 +125,9 @@ html, body {
110
  100% { opacity: 1; transform: translateY(0); }
111
  }
112
 
113
- .animate-pulse-glow { animation: pulse-glow 2s ease-in-out infinite; }
114
- .animate-float { animation: float 3s ease-in-out infinite; }
 
115
  .animate-emotion { animation: emotion-appear 0.3s ease-out forwards; }
116
  .animate-fade-in-up { animation: fade-in-up 0.5s ease-out forwards; }
117
  .animate-blink { animation: blink 1.5s ease-in-out infinite; }
@@ -124,38 +140,27 @@ html, body {
124
  .emotion-surprised { color: #f6ad55; }
125
  .emotion-neutral { color: #a0aec0; }
126
 
127
- .emotion-bg-happy { background: rgba(104, 211, 145, 0.15); border-color: rgba(104, 211, 145, 0.4); }
128
- .emotion-bg-sad { background: rgba(118, 228, 247, 0.15); border-color: rgba(118, 228, 247, 0.4); }
129
- .emotion-bg-angry { background: rgba(252, 129, 129, 0.15); border-color: rgba(252, 129, 129, 0.4); }
130
- .emotion-bg-surprised { background: rgba(246, 173, 85, 0.15); border-color: rgba(246, 173, 85, 0.4); }
131
- .emotion-bg-neutral { background: rgba(160, 174, 192, 0.12); border-color: rgba(160, 174, 192, 0.3); }
132
 
133
- /* Video scan effect */
134
- .scan-overlay::after {
135
- content: '';
136
- position: absolute;
137
- width: 100%;
138
- height: 2px;
139
- background: linear-gradient(90deg, transparent, rgba(99, 179, 237, 0.6), transparent);
140
- animation: scan-line 3s linear infinite;
141
- pointer-events: none;
142
  }
143
 
144
- /* Corner brackets */
145
- .corner-bracket::before,
146
- .corner-bracket::after {
147
- content: '';
148
- position: absolute;
149
- width: 20px;
150
- height: 20px;
151
- border-color: rgba(99, 179, 237, 0.6);
152
- border-style: solid;
153
  }
154
- .corner-bracket::before {
155
- top: 8px; left: 8px;
156
- border-width: 2px 0 0 2px;
 
157
  }
158
- .corner-bracket::after {
159
- bottom: 8px; right: 8px;
160
- border-width: 0 2px 2px 0;
161
  }
 
 
39
 
40
  /* Glassmorphism */
41
  .glass {
42
+ background: rgba(255, 255, 255, 0.03);
43
+ backdrop-filter: blur(12px);
44
+ -webkit-backdrop-filter: blur(12px);
45
  border: 1px solid rgba(255, 255, 255, 0.08);
46
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
47
  }
48
 
49
  .glass-strong {
50
+ background: rgba(255, 255, 255, 0.06);
51
+ backdrop-filter: blur(24px);
52
+ -webkit-backdrop-filter: blur(24px);
53
  border: 1px solid rgba(255, 255, 255, 0.12);
54
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
55
  }
56
 
57
  /* Gradient text */
 
60
  -webkit-background-clip: text;
61
  -webkit-text-fill-color: transparent;
62
  background-clip: text;
63
+ font-weight: 700;
64
  }
65
 
66
+ /* High-Tech Borders */
67
+ .cyber-border {
68
+ position: relative;
69
+ border: 1px solid rgba(99, 179, 237, 0.2);
 
 
 
70
  }
71
 
72
+ .cyber-border::before {
73
+ content: '';
74
+ position: absolute;
75
+ top: -1px; left: -1px; right: -1px; bottom: -1px;
76
+ background: linear-gradient(45deg, transparent, rgba(99, 179, 237, 0.3), transparent);
77
+ z-index: -1;
78
+ border-radius: inherit;
79
+ opacity: 0;
80
+ transition: opacity 0.3s;
81
  }
82
 
83
+ .cyber-border:hover::before {
84
+ opacity: 1;
85
+ }
86
+
87
+ /* Animations */
88
+ @keyframes slide-in-right {
89
+ 0% { opacity: 0; transform: translateX(30px); }
90
+ 100% { opacity: 1; transform: translateX(0); }
91
+ }
92
+
93
+ @keyframes scale-up {
94
+ 0% { opacity: 0; transform: scale(0.95); }
95
+ 100% { opacity: 1; transform: scale(1); }
96
+ }
97
+
98
+ @keyframes glow-pulse {
99
+ 0%, 100% { opacity: 0.5; }
100
+ 50% { opacity: 1; }
101
  }
102
 
103
  @keyframes scan-line {
 
110
  100% { opacity: 1; transform: scale(1) translateY(0px); }
111
  }
112
 
 
 
 
 
 
113
  @keyframes blink {
114
  0%, 100% { opacity: 1; }
115
  50% { opacity: 0.3; }
 
125
  100% { opacity: 1; transform: translateY(0); }
126
  }
127
 
128
+ .animate-slide-in { animation: slide-in-right 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
129
+ .animate-scale-up { animation: scale-up 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
130
+ .animate-glow { animation: glow-pulse 2s ease-in-out infinite; }
131
  .animate-emotion { animation: emotion-appear 0.3s ease-out forwards; }
132
  .animate-fade-in-up { animation: fade-in-up 0.5s ease-out forwards; }
133
  .animate-blink { animation: blink 1.5s ease-in-out infinite; }
 
140
  .emotion-surprised { color: #f6ad55; }
141
  .emotion-neutral { color: #a0aec0; }
142
 
143
+ .emotion-bg-happy { background: rgba(104, 211, 145, 0.1); border-color: rgba(104, 211, 145, 0.2); }
144
+ .emotion-bg-sad { background: rgba(118, 228, 247, 0.1); border-color: rgba(118, 228, 247, 0.2); }
145
+ .emotion-bg-angry { background: rgba(252, 129, 129, 0.1); border-color: rgba(252, 129, 129, 0.2); }
146
+ .emotion-bg-surprised { background: rgba(246, 173, 85, 0.1); border-color: rgba(246, 173, 85, 0.2); }
147
+ .emotion-bg-neutral { background: rgba(160, 174, 192, 0.08); border-color: rgba(160, 174, 192, 0.15); }
148
 
149
+ /* Custom Scrollbar */
150
+ ::-webkit-scrollbar {
151
+ width: 5px;
 
 
 
 
 
 
152
  }
153
 
154
+ ::-webkit-scrollbar-track {
155
+ background: transparent;
 
 
 
 
 
 
 
156
  }
157
+
158
+ ::-webkit-scrollbar-thumb {
159
+ background: rgba(255, 255, 255, 0.1);
160
+ border-radius: 10px;
161
  }
162
+
163
+ ::-webkit-scrollbar-thumb:hover {
164
+ background: rgba(255, 255, 255, 0.2);
165
  }
166
+