Anish commited on
Commit
825fa02
·
1 Parent(s): 67264dd

[Minor changes to Frontend]

Browse files
backend/app/api/auth_routes.py CHANGED
@@ -134,5 +134,6 @@ def get_current_active_user(
134
  "name": getattr(current_user, 'username', getattr(current_user, 'email', 'User')),
135
  "is_admin": getattr(current_user, 'is_admin', False),
136
  "avatar_url": getattr(current_user, 'avatar_url', None),
137
- "google_avatar_url": getattr(current_user, 'google_avatar_url', None)
 
138
  }
 
134
  "name": getattr(current_user, 'username', getattr(current_user, 'email', 'User')),
135
  "is_admin": getattr(current_user, 'is_admin', False),
136
  "avatar_url": getattr(current_user, 'avatar_url', None),
137
+ "google_avatar_url": getattr(current_user, 'google_avatar_url', None),
138
+ "created_at": current_user.created_at.isoformat() if getattr(current_user, 'created_at', None) else None
139
  }
frontend/app/dashboard/page.tsx CHANGED
@@ -11,7 +11,7 @@ import { motion, AnimatePresence } from "framer-motion";
11
  import { useRouter } from "next/navigation";
12
 
13
  export default function DashboardPage() {
14
- const { isAuthenticated, loading, logout } = useAuth();
15
  const router = useRouter();
16
 
17
  const [files, setFiles] = useState<any[]>([]);
@@ -34,6 +34,16 @@ export default function DashboardPage() {
34
  }
35
  }, [isAuthenticated, loading, router]);
36
 
 
 
 
 
 
 
 
 
 
 
37
  const handleDeleteFile = async (id: string, filename: string) => {
38
  try {
39
  await apiLayer.deleteFile(id);
@@ -196,6 +206,9 @@ export default function DashboardPage() {
196
  animate={{ opacity: 1, backdropFilter: 'blur(20px)' }}
197
  exit={{ opacity: 0, backdropFilter: 'blur(0px)' }}
198
  className="fixed inset-0 z-[100] bg-black/60 flex justify-center items-center p-4"
 
 
 
199
  >
200
  <motion.div
201
  initial={{ scale: 0.95, y: 20 }}
 
11
  import { useRouter } from "next/navigation";
12
 
13
  export default function DashboardPage() {
14
+ const { isAuthenticated, loading } = useAuth();
15
  const router = useRouter();
16
 
17
  const [files, setFiles] = useState<any[]>([]);
 
34
  }
35
  }, [isAuthenticated, loading, router]);
36
 
37
+ // Scroll Lock for Modal
38
+ useEffect(() => {
39
+ if (isUploadModalOpen) {
40
+ document.body.style.overflow = 'hidden';
41
+ } else {
42
+ document.body.style.overflow = '';
43
+ }
44
+ return () => { document.body.style.overflow = ''; };
45
+ }, [isUploadModalOpen]);
46
+
47
  const handleDeleteFile = async (id: string, filename: string) => {
48
  try {
49
  await apiLayer.deleteFile(id);
 
206
  animate={{ opacity: 1, backdropFilter: 'blur(20px)' }}
207
  exit={{ opacity: 0, backdropFilter: 'blur(0px)' }}
208
  className="fixed inset-0 z-[100] bg-black/60 flex justify-center items-center p-4"
209
+ onClick={(e) => {
210
+ if (e.target === e.currentTarget) setIsUploadModalOpen(false);
211
+ }}
212
  >
213
  <motion.div
214
  initial={{ scale: 0.95, y: 20 }}
frontend/app/globals.css CHANGED
@@ -56,7 +56,7 @@
56
  :root {
57
  --theme-bg-val: #080c14;
58
  --theme-text-val: #fde8d6;
59
- --theme-border-val: rgba(255, 255, 255, 0.05);
60
  --theme-glass-val: rgba(8, 12, 20, 0.8);
61
  --theme-glass-border-val: rgba(255, 255, 255, 0.1);
62
  --theme-bg: var(--theme-bg-val);
 
56
  :root {
57
  --theme-bg-val: #080c14;
58
  --theme-text-val: #fde8d6;
59
+ --theme-border-val: #ffffff0d;
60
  --theme-glass-val: rgba(8, 12, 20, 0.8);
61
  --theme-glass-border-val: rgba(255, 255, 255, 0.1);
62
  --theme-bg: var(--theme-bg-val);
frontend/app/layout.tsx CHANGED
@@ -18,8 +18,8 @@ const geistMono = Geist_Mono({
18
  });
19
 
20
  export const metadata: Metadata = {
21
- title: "Create Next App",
22
- description: "Generated by create next app",
23
  };
24
 
25
  import { AuthProvider } from "@/contexts/AuthContext";
 
18
  });
19
 
20
  export const metadata: Metadata = {
21
+ title: "Spotix",
22
+ description: "Detect AI generated Image/Video",
23
  };
24
 
25
  import { AuthProvider } from "@/contexts/AuthContext";
frontend/app/login/page.tsx CHANGED
@@ -94,7 +94,7 @@ export default function LoginPage() {
94
  };
95
 
96
  return (
97
- <div id="login-section" className="min-h-[100vh] bg-[var(--theme-bg)] text-[var(--theme-text)] font-sans relative flex justify-center pt-[15vh] px-4 overflow-y-auto overflow-x-hidden pb-32">
98
  <style dangerouslySetInnerHTML={{
99
  __html: `
100
  html, body, a, button, [role="button"], input, select, textarea, .cursor-pointer { cursor: none !important; }
@@ -137,7 +137,7 @@ export default function LoginPage() {
137
  <div className="absolute -top-40 -right-40 w-80 h-80 bg-theme-text/5 rounded-full blur-[80px] pointer-events-none"></div>
138
 
139
  <h1 className="text-3xl tracking-widest font-bold mb-2 font-sans uppercase">
140
- Neural<span className="text-[var(--theme-border)]/50">Vault</span> Access
141
  </h1>
142
  <p className="text-[#d0c4bb]/60 text-sm mb-6">Authenticate to seamlessly vault and analyze.</p>
143
 
 
94
  };
95
 
96
  return (
97
+ <div id="login-section" className="min-h-[100vh] bg-[var(--theme-bg)] text-[var(--theme-text)] font-sans relative flex justify-center pt-[10vh] px-4 overflow-y-auto overflow-x-hidden">
98
  <style dangerouslySetInnerHTML={{
99
  __html: `
100
  html, body, a, button, [role="button"], input, select, textarea, .cursor-pointer { cursor: none !important; }
 
137
  <div className="absolute -top-40 -right-40 w-80 h-80 bg-theme-text/5 rounded-full blur-[80px] pointer-events-none"></div>
138
 
139
  <h1 className="text-3xl tracking-widest font-bold mb-2 font-sans uppercase">
140
+ Neural<span className="text-[var(--theme-text)]/50"> Vault</span> Access
141
  </h1>
142
  <p className="text-[#d0c4bb]/60 text-sm mb-6">Authenticate to seamlessly vault and analyze.</p>
143
 
frontend/app/not-found.tsx CHANGED
@@ -28,6 +28,7 @@ export default function NotFoundPage() {
28
 
29
  .glitch-text {
30
  position: relative;
 
31
  }
32
  .glitch-text::before, .glitch-text::after {
33
  content: attr(data-text);
@@ -38,26 +39,6 @@ export default function NotFoundPage() {
38
  height: 100%;
39
  opacity: 0.8;
40
  }
41
- .glitch-text::before {
42
- left: 2px;
43
- text-shadow: -2px 0 red;
44
- clip: rect(24px, 550px, 90px, 0);
45
- animation: glitch-anim 3s infinite linear alternate-reverse;
46
- }
47
- .glitch-text::after {
48
- left: -2px;
49
- text-shadow: -2px 0 blue;
50
- clip: rect(85px, 550px, 140px, 0);
51
- animation: glitch-anim 2.5s infinite linear alternate-reverse;
52
- }
53
- @keyframes glitch-anim {
54
- 0% { clip: rect(23px, 9999px, 85px, 0); }
55
- 20% { clip: rect(98px, 9999px, 14px, 0); }
56
- 40% { clip: rect(43px, 9999px, 65px, 0); }
57
- 60% { clip: rect(12px, 9999px, 92px, 0); }
58
- 80% { clip: rect(76px, 9999px, 34px, 0); }
59
- 100% { clip: rect(54px, 9999px, 12px, 0); }
60
- }
61
  `
62
  }} />
63
 
@@ -69,16 +50,8 @@ export default function NotFoundPage() {
69
  backgroundSize: '40px 40px'
70
  }}></div>
71
 
72
- {/* Dynamic Spotlight */}
73
- <div
74
- className="absolute inset-0 pointer-events-none z-0 mix-blend-screen opacity-20 transition-opacity duration-300"
75
- style={{
76
- background: `radial-gradient(circle 400px at ${mousePos.x}px ${mousePos.y}px, var(--theme-text), transparent 80%)`
77
- }}
78
- />
79
-
80
  <div className="flex-1 flex flex-col items-center justify-center relative z-10 px-6 text-center">
81
- <div className="mb-8 relative dash-border rounded-full p-6 bg-[var(--theme-text)]/5 shadow-[0_0_80px_rgba(253,232,214,0.1)]">
82
  <AlertTriangle className="w-16 h-16 text-red-500/80 animate-pulse !cursor-none" />
83
  </div>
84
 
@@ -87,19 +60,19 @@ export default function NotFoundPage() {
87
  </h1>
88
 
89
  <div className="bg-red-500/10 border border-red-500/20 text-red-400 font-mono uppercase tracking-[0.3em] text-xs px-4 py-2 rounded-full mb-8 backdrop-blur-md !cursor-none">
90
- SIGNAL LOST // ENDPOINT NOT FOUND
91
  </div>
92
 
93
  <p className="text-[#d0c4bb] max-w-lg mb-12 text-lg font-light leading-relaxed !cursor-none">
94
- The requested temporal coordinate does not exist within the Obsidian network. The neural link has been severed.
95
  </p>
96
 
97
  <button
98
  onClick={() => router.push('/')}
99
- className="group relative overflow-hidden rounded-full bg-[var(--theme-text)] text-[var(--theme-bg)] px-10 py-4 font-bold tracking-widest text-sm uppercase transition-all duration-500 hover:shadow-[0_0_40px_rgba(253,232,214,0.3)] hover:scale-105 !cursor-none flex items-center gap-3"
100
  >
101
- <ArrowLeft className="w-5 h-5 transition-transform duration-300 group-hover:-translate-x-1 !cursor-none" />
102
- <span>Return to Gateway</span>
103
  <div className="absolute inset-0 bg-white/20 translate-y-[100%] group-hover:translate-y-[0%] transition-transform duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] rounded-full !cursor-none"></div>
104
  </button>
105
  </div>
 
28
 
29
  .glitch-text {
30
  position: relative;
31
+ font-size: 200px;
32
  }
33
  .glitch-text::before, .glitch-text::after {
34
  content: attr(data-text);
 
39
  height: 100%;
40
  opacity: 0.8;
41
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  `
43
  }} />
44
 
 
50
  backgroundSize: '40px 40px'
51
  }}></div>
52
 
 
 
 
 
 
 
 
 
53
  <div className="flex-1 flex flex-col items-center justify-center relative z-10 px-6 text-center">
54
+ <div className="mt-20 relative dash-border rounded-full p-6 bg-[var(--theme-text)]/5 shadow-[0_0_80px_rgba(253,232,214,0.1)]">
55
  <AlertTriangle className="w-16 h-16 text-red-500/80 animate-pulse !cursor-none" />
56
  </div>
57
 
 
60
  </h1>
61
 
62
  <div className="bg-red-500/10 border border-red-500/20 text-red-400 font-mono uppercase tracking-[0.3em] text-xs px-4 py-2 rounded-full mb-8 backdrop-blur-md !cursor-none">
63
+ ERROR // ENDPOINT NOT FOUND //
64
  </div>
65
 
66
  <p className="text-[#d0c4bb] max-w-lg mb-12 text-lg font-light leading-relaxed !cursor-none">
67
+ The requested temporal coordinate does not exist within the Spotix network.
68
  </p>
69
 
70
  <button
71
  onClick={() => router.push('/')}
72
+ className="group relative overflow-hidden rounded-full bg-[var(--theme-text)] text-[var(--theme-bg)] px-10 py-4 font-bold tracking-widest text-sm uppercase transition-all duration-100 hover:shadow-[0_0_40px_rgba(253,232,214,0.1)] hover:scale-105 !cursor-none flex items-center gap-3"
73
  >
74
+ <ArrowLeft className="w-5 h-5 transition-transform duration-100 group-hover:-translate-x-1 !cursor-none" />
75
+ <span>Return to Spotix</span>
76
  <div className="absolute inset-0 bg-white/20 translate-y-[100%] group-hover:translate-y-[0%] transition-transform duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] rounded-full !cursor-none"></div>
77
  </button>
78
  </div>
frontend/app/page.tsx CHANGED
@@ -58,6 +58,16 @@ export default function LandingPage() {
58
  return () => window.removeEventListener('keydown', handleKeyDown);
59
  }, []);
60
 
 
 
 
 
 
 
 
 
 
 
61
  // Global Cursor & Interactions
62
  // Note: The global custom cursor is now managed by components/shared/CustomCursor.tsx
63
 
@@ -273,7 +283,7 @@ export default function LandingPage() {
273
  <div className="loader-panel top"></div>
274
  <div className="loader-panel bottom"></div>
275
  <div className="loader-content flex flex-col items-center">
276
- <div className="text-[10px] font-mono tracking-[0.5em] text-[var(--theme-text)] uppercase mb-4">Initializing Obsidian Engine</div>
277
  <div id="progress-bar-container">
278
  <div id="progress-bar-fill" style={{ width: `${loadProgress}%` }}></div>
279
  </div>
 
58
  return () => window.removeEventListener('keydown', handleKeyDown);
59
  }, []);
60
 
61
+ // Scroll Lock for Modal
62
+ useEffect(() => {
63
+ if (isUploadModalOpen) {
64
+ document.body.style.overflow = 'hidden';
65
+ } else {
66
+ document.body.style.overflow = '';
67
+ }
68
+ return () => { document.body.style.overflow = ''; };
69
+ }, [isUploadModalOpen]);
70
+
71
  // Global Cursor & Interactions
72
  // Note: The global custom cursor is now managed by components/shared/CustomCursor.tsx
73
 
 
283
  <div className="loader-panel top"></div>
284
  <div className="loader-panel bottom"></div>
285
  <div className="loader-content flex flex-col items-center">
286
+ <div className="text-[10px] font-mono tracking-[0.5em] text-[var(--theme-text)] uppercase mb-4">Initializing Spotix Engine</div>
287
  <div id="progress-bar-container">
288
  <div id="progress-bar-fill" style={{ width: `${loadProgress}%` }}></div>
289
  </div>
frontend/app/profile/page.tsx CHANGED
@@ -7,7 +7,7 @@ import { apiLayer } from "@/lib/api";
7
  import { Camera, Mail, Shield, CheckCircle, Activity, Calendar, Trash2 } from "lucide-react";
8
 
9
  export default function ProfilePage() {
10
- const { user, isAuthenticated, loading } = useAuth();
11
  const [stats, setStats] = useState({ totalAnalyses: 0 });
12
 
13
  // Edit States
@@ -46,6 +46,7 @@ export default function ProfilePage() {
46
 
47
  try {
48
  await apiLayer.updateUsername(usernameInput);
 
49
  setStatusMsg({ type: "success", text: "Username updated successfully." });
50
  setIsEditingUsername(false);
51
  setTimeout(() => setStatusMsg(null), 3000);
@@ -69,6 +70,7 @@ export default function ProfilePage() {
69
  setLocalAvatar(base64String);
70
  try {
71
  await apiLayer.updateAvatar(base64String);
 
72
  setStatusMsg({ type: "success", text: "Avatar updated successfully." });
73
  setTimeout(() => setStatusMsg(null), 3000);
74
  } catch (err) {
 
7
  import { Camera, Mail, Shield, CheckCircle, Activity, Calendar, Trash2 } from "lucide-react";
8
 
9
  export default function ProfilePage() {
10
+ const { user, isAuthenticated, loading, updateUser } = useAuth();
11
  const [stats, setStats] = useState({ totalAnalyses: 0 });
12
 
13
  // Edit States
 
46
 
47
  try {
48
  await apiLayer.updateUsername(usernameInput);
49
+ updateUser({ name: usernameInput });
50
  setStatusMsg({ type: "success", text: "Username updated successfully." });
51
  setIsEditingUsername(false);
52
  setTimeout(() => setStatusMsg(null), 3000);
 
70
  setLocalAvatar(base64String);
71
  try {
72
  await apiLayer.updateAvatar(base64String);
73
+ updateUser({ avatar_url: base64String });
74
  setStatusMsg({ type: "success", text: "Avatar updated successfully." });
75
  setTimeout(() => setStatusMsg(null), 3000);
76
  } catch (err) {
frontend/app/result/[id]/page.tsx CHANGED
@@ -40,12 +40,25 @@ export default function ResultPage() {
40
 
41
  useEffect(() => {
42
  const handleKeyDown = (e: KeyboardEvent) => {
43
- if (e.key === 'Escape') setShowAuthModal(false);
 
 
 
44
  };
45
  window.addEventListener('keydown', handleKeyDown);
46
  return () => window.removeEventListener('keydown', handleKeyDown);
47
  }, []);
48
 
 
 
 
 
 
 
 
 
 
 
49
  useEffect(() => {
50
  let dragTimer: NodeJS.Timeout;
51
  const handleDragOver = (e: DragEvent) => {
@@ -211,7 +224,7 @@ export default function ResultPage() {
211
  <div className="absolute inset-0 flex flex-col items-center justify-center z-50">
212
  <div className="font-mono text-sm uppercase tracking-widest text-[#d0c4bb] mb-6">Error Establishing Uplink</div>
213
  <button onClick={() => router.push("/")} className="bg-[var(--theme-text)]/10 px-8 py-3 rounded-full hover:bg-[var(--theme-text)]/20 transition-all text-sm font-bold border border-theme-border !cursor-none group">
214
- Return to Platform
215
  </button>
216
  </div>
217
  ) : (
@@ -239,7 +252,7 @@ export default function ResultPage() {
239
  {/* Left Side: Media Preview */}
240
  <div className="lg:w-1/2 flex flex-col gap-6">
241
  <div className="flex justify-between items-end mb-2 relative">
242
- <h2 className="text-sm font-mono tracking-widest text-[var(--theme-text)]/90 uppercase">Extracted File</h2>
243
  {heatmapUrl && (
244
  <div className="text-xs font-semibold uppercase tracking-wider text-[var(--theme-text)] bg-theme-text/5 px-4 py-1.5 rounded-full border border-theme-border">
245
  DRAG TO REVEAL HEATMAP
@@ -318,14 +331,14 @@ export default function ResultPage() {
318
  verdictStatus === 'SUSPICIOUS' ? 'text-amber-400' :
319
  'text-emerald-400'
320
  }`}>
321
- Neural Verdict
322
  </span>
323
- <h1 className="text-5xl md:text-6xl font-black tracking-tight text-[var(--theme-text)] my-2">
324
  {label}
325
  </h1>
326
  {confidenceVal !== null && (
327
- <div className="text-3xl font-light text-[var(--theme-border)]">
328
- {confidenceVal.toFixed(1)}% <span className="text-lg opacity-60">Confidence</span>
329
  </div>
330
  )}
331
  {nsfwScore !== null && (
@@ -339,9 +352,9 @@ export default function ResultPage() {
339
  {/* Diagnostic Explanation Box */}
340
  <div className="upload-glass p-8 rounded-3xl relative overflow-hidden group">
341
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/20 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div>
342
- <h3 className="text-sm font-semibold uppercase tracking-widest text-[#d0c4bb] mb-4">Diagnostic Output</h3>
343
  <p className="text-sm text-theme-text/80 leading-relaxed font-light">
344
- {fileData?.ai_explanation ? fileData.ai_explanation : 'The neural engine has evaluated structural frequencies and edge patterns. No anomalies flagged in standard distribution.'}
345
  </p>
346
  </div>
347
 
@@ -349,10 +362,10 @@ export default function ResultPage() {
349
  <div className="grid grid-cols-1 md:grid-cols-2 gap-5">
350
  <div className="upload-glass p-6 rounded-2xl flex flex-col gap-4 !cursor-none relative overflow-hidden group">
351
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/30 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div>
352
- <Activity className="w-6 h-6 text-[var(--theme-border)] opacity-80" />
353
  <div>
354
  <h4 className="text-[var(--theme-text)] font-bold text-lg mb-1">Neural Fingerprint</h4>
355
- <p className="text-[#d0c4bb] text-xs leading-relaxed opacity-80 mb-3">Frequency bounds evaluated across 512 discrete localized convolutions.</p>
356
  {freqScore !== null && (
357
  <div className="flex items-end gap-2">
358
  <span className="text-2xl font-mono text-emerald-400/90">{freqScore}</span>
@@ -364,7 +377,7 @@ export default function ResultPage() {
364
 
365
  <div className="upload-glass p-6 rounded-2xl flex flex-col gap-4 !cursor-none relative overflow-hidden group">
366
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/30 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000 delay-100"></div>
367
- <Layers className="w-6 h-6 text-[var(--theme-border)] opacity-80" />
368
  <div>
369
  <h4 className="text-[var(--theme-text)] font-bold text-lg mb-1">Noise Pattern</h4>
370
  <p className="text-[#d0c4bb] text-xs leading-relaxed opacity-80 mb-3">Evaluating PRNU map distribution. Sensor consistency is within normal threshold.</p>
@@ -379,9 +392,9 @@ export default function ResultPage() {
379
 
380
  <div className="upload-glass p-6 rounded-2xl flex flex-col gap-4 !cursor-none md:col-span-2 relative overflow-hidden group">
381
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/30 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000 delay-200"></div>
382
- <Hash className="w-6 h-6 text-[var(--theme-border)] opacity-80" />
383
  <div>
384
- <h4 className="text-[var(--theme-text)] font-bold text-lg mb-1">Detection Signals</h4>
385
  <p className="text-[#d0c4bb] text-xs leading-relaxed opacity-80 flex flex-wrap gap-2">
386
  {displayTags.map(tag => (
387
  <span key={tag} className="inline-block px-3 py-1 bg-theme-text/5 rounded-md border border-theme-border font-mono capitalize shadow-sm">
 
40
 
41
  useEffect(() => {
42
  const handleKeyDown = (e: KeyboardEvent) => {
43
+ if (e.key === 'Escape') {
44
+ setShowAuthModal(false);
45
+ setIsUploadModalOpen(false);
46
+ }
47
  };
48
  window.addEventListener('keydown', handleKeyDown);
49
  return () => window.removeEventListener('keydown', handleKeyDown);
50
  }, []);
51
 
52
+ // Scroll Lock for Modal
53
+ useEffect(() => {
54
+ if (isUploadModalOpen) {
55
+ document.body.style.overflow = 'hidden';
56
+ } else {
57
+ document.body.style.overflow = '';
58
+ }
59
+ return () => { document.body.style.overflow = ''; };
60
+ }, [isUploadModalOpen]);
61
+
62
  useEffect(() => {
63
  let dragTimer: NodeJS.Timeout;
64
  const handleDragOver = (e: DragEvent) => {
 
224
  <div className="absolute inset-0 flex flex-col items-center justify-center z-50">
225
  <div className="font-mono text-sm uppercase tracking-widest text-[#d0c4bb] mb-6">Error Establishing Uplink</div>
226
  <button onClick={() => router.push("/")} className="bg-[var(--theme-text)]/10 px-8 py-3 rounded-full hover:bg-[var(--theme-text)]/20 transition-all text-sm font-bold border border-theme-border !cursor-none group">
227
+ RETURN TO PLATFORM
228
  </button>
229
  </div>
230
  ) : (
 
252
  {/* Left Side: Media Preview */}
253
  <div className="lg:w-1/2 flex flex-col gap-6">
254
  <div className="flex justify-between items-end mb-2 relative">
255
+ <h2 className="text-sm font-mono tracking-widest text-[var(--theme-text)]/90 uppercase">Analyzed File</h2>
256
  {heatmapUrl && (
257
  <div className="text-xs font-semibold uppercase tracking-wider text-[var(--theme-text)] bg-theme-text/5 px-4 py-1.5 rounded-full border border-theme-border">
258
  DRAG TO REVEAL HEATMAP
 
331
  verdictStatus === 'SUSPICIOUS' ? 'text-amber-400' :
332
  'text-emerald-400'
333
  }`}>
334
+ Analysis Verdict
335
  </span>
336
+ <h1 className="text-5xs md:text-6xl font-black tracking-tight text-[var(--theme-text)] my-2">
337
  {label}
338
  </h1>
339
  {confidenceVal !== null && (
340
+ <div className="text-3xl font-light text-[var(--theme-text)]">
341
+ {confidenceVal.toFixed(1)}% <span className="text-lg opacity-100">Confidence</span>
342
  </div>
343
  )}
344
  {nsfwScore !== null && (
 
352
  {/* Diagnostic Explanation Box */}
353
  <div className="upload-glass p-8 rounded-3xl relative overflow-hidden group">
354
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/20 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div>
355
+ <h3 className="text-sm font-semibold uppercase tracking-widest text-[#d0c4bb] mb-4">AI Explanation</h3>
356
  <p className="text-sm text-theme-text/80 leading-relaxed font-light">
357
+ {fileData?.ai_explanation ? fileData.ai_explanation : 'The Spotix engine has evaluated structural frequencies and edge patterns. No anomalies flagged in standard distribution.'}
358
  </p>
359
  </div>
360
 
 
362
  <div className="grid grid-cols-1 md:grid-cols-2 gap-5">
363
  <div className="upload-glass p-6 rounded-2xl flex flex-col gap-4 !cursor-none relative overflow-hidden group">
364
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/30 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000"></div>
365
+ <Activity className="w-6 h-6 text-[var(--theme-text)] opacity-80" />
366
  <div>
367
  <h4 className="text-[var(--theme-text)] font-bold text-lg mb-1">Neural Fingerprint</h4>
368
+ <p className="text-[#d0c4bb] text-xs leading-relaxed opacity-80 mb-8">Frequency bounds evaluated across 512 discrete localized convolutions.</p>
369
  {freqScore !== null && (
370
  <div className="flex items-end gap-2">
371
  <span className="text-2xl font-mono text-emerald-400/90">{freqScore}</span>
 
377
 
378
  <div className="upload-glass p-6 rounded-2xl flex flex-col gap-4 !cursor-none relative overflow-hidden group">
379
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/30 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000 delay-100"></div>
380
+ <Layers className="w-6 h-6 text-[var(--theme-text)] opacity-80" />
381
  <div>
382
  <h4 className="text-[var(--theme-text)] font-bold text-lg mb-1">Noise Pattern</h4>
383
  <p className="text-[#d0c4bb] text-xs leading-relaxed opacity-80 mb-3">Evaluating PRNU map distribution. Sensor consistency is within normal threshold.</p>
 
392
 
393
  <div className="upload-glass p-6 rounded-2xl flex flex-col gap-4 !cursor-none md:col-span-2 relative overflow-hidden group">
394
  <div className="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-[var(--theme-border)]/30 to-transparent transform -translate-x-full group-hover:translate-x-full transition-transform duration-1000 delay-200"></div>
395
+ <Hash className="w-6 h-6 text-[var(--theme-text)] opacity-80" />
396
  <div>
397
+ <h4 className="text-[var(--theme-text)] font-bold text-lg mb-4">Detection Tags</h4>
398
  <p className="text-[#d0c4bb] text-xs leading-relaxed opacity-80 flex flex-wrap gap-2">
399
  {displayTags.map(tag => (
400
  <span key={tag} className="inline-block px-3 py-1 bg-theme-text/5 rounded-md border border-theme-border font-mono capitalize shadow-sm">
frontend/components/dashboard/AnalyticsSection.tsx CHANGED
@@ -52,16 +52,16 @@ export default function AnalyticsSection({ files }: { files: any[] }) {
52
  {/* Quick Stats Column */}
53
  <div className="flex flex-col gap-6 lg:col-span-1">
54
  <div className="p-6 bg-[var(--theme-text)]/5 border border-[var(--theme-border)] rounded-2xl dash-border hover:bg-[var(--theme-text)]/10 transition backdrop-blur-sm">
55
- <h3 className="text-[var(--theme-text)]/40 uppercase tracking-widest text-xs mb-2">Total Analyses</h3>
56
  <p className="text-4xl font-bold font-mono tracking-tighter text-[var(--theme-text)]">{stats.summary.total}</p>
57
  </div>
58
  <div className="grid grid-cols-2 gap-6">
59
  <div className="p-6 bg-[var(--theme-text)]/5 border border-[var(--theme-border)] rounded-2xl hover:border-red-500/50 transition backdrop-blur-sm group">
60
- <h3 className="text-[var(--theme-text)]/40 uppercase tracking-widest text-[10px] mb-2">Synthetic</h3>
61
  <p className="text-2xl font-bold font-mono text-red-400 group-hover:text-red-300 transition">{stats.summary.ai}%</p>
62
  </div>
63
  <div className="p-6 bg-[var(--theme-text)]/5 border border-[var(--theme-border)] rounded-2xl hover:border-green-500/50 transition backdrop-blur-sm group">
64
- <h3 className="text-[var(--theme-text)]/40 uppercase tracking-widest text-[10px] mb-2">Authentic</h3>
65
  <p className="text-2xl font-bold font-mono text-green-400 group-hover:text-green-300 transition">{stats.summary.real}%</p>
66
  </div>
67
  </div>
 
52
  {/* Quick Stats Column */}
53
  <div className="flex flex-col gap-6 lg:col-span-1">
54
  <div className="p-6 bg-[var(--theme-text)]/5 border border-[var(--theme-border)] rounded-2xl dash-border hover:bg-[var(--theme-text)]/10 transition backdrop-blur-sm">
55
+ <h3 className="text-[var(--theme-text)]/60 uppercase tracking-widest text-[14px] mb-4">Total Analyses</h3>
56
  <p className="text-4xl font-bold font-mono tracking-tighter text-[var(--theme-text)]">{stats.summary.total}</p>
57
  </div>
58
  <div className="grid grid-cols-2 gap-6">
59
  <div className="p-6 bg-[var(--theme-text)]/5 border border-[var(--theme-border)] rounded-2xl hover:border-red-500/50 transition backdrop-blur-sm group">
60
+ <h3 className="text-[var(--theme-text)]/40 uppercase tracking-widest text-[14px] mb-6.5">Synthetic</h3>
61
  <p className="text-2xl font-bold font-mono text-red-400 group-hover:text-red-300 transition">{stats.summary.ai}%</p>
62
  </div>
63
  <div className="p-6 bg-[var(--theme-text)]/5 border border-[var(--theme-border)] rounded-2xl hover:border-green-500/50 transition backdrop-blur-sm group">
64
+ <h3 className="text-[var(--theme-text)]/40 uppercase tracking-widest text-[14px] mb-6.5">Authentic</h3>
65
  <p className="text-2xl font-bold font-mono text-green-400 group-hover:text-green-300 transition">{stats.summary.real}%</p>
66
  </div>
67
  </div>
frontend/components/dashboard/HistoryGrid.tsx CHANGED
@@ -42,7 +42,7 @@ export default function HistoryGrid({ files, onDeleteFile }: { files: any[], onD
42
  <Clock className="w-6 h-6 text-[var(--theme-text)]/20 !cursor-none" />
43
  </div>
44
  <p className="text-[var(--theme-text)]/40 mb-2 font-mono text-sm tracking-widest uppercase !cursor-none">No analyses yet</p>
45
- <p className="text-[var(--theme-text)]/30 text-xs text-center max-w-sm !cursor-none">Upload media using the button above to begin generating your detection history.</p>
46
  </div>
47
  );
48
  }
@@ -149,7 +149,7 @@ export default function HistoryGrid({ files, onDeleteFile }: { files: any[], onD
149
  </div>
150
  <div className="p-6 !cursor-none">
151
  <p className="text-[var(--theme-text)]/70 text-sm mb-6 !cursor-none">
152
- Are you sure you want to delete <strong className="text-[var(--theme-text)] font-mono">{fileToDelete.filename}</strong>? This action cannot be undone and will permanently remove the item from the Obsidian Engine.
153
  </p>
154
  <div className="flex gap-3 !cursor-none">
155
  <button
 
42
  <Clock className="w-6 h-6 text-[var(--theme-text)]/20 !cursor-none" />
43
  </div>
44
  <p className="text-[var(--theme-text)]/40 mb-2 font-mono text-sm tracking-widest uppercase !cursor-none">No analyses yet</p>
45
+ <p className="text-[var(--theme-text)]/30 text-xs text-center max-w-sm !cursor-none">Upload media using the [Analyze New Media] button above to begin generating your detection history.</p>
46
  </div>
47
  );
48
  }
 
149
  </div>
150
  <div className="p-6 !cursor-none">
151
  <p className="text-[var(--theme-text)]/70 text-sm mb-6 !cursor-none">
152
+ Are you sure you want to delete <strong className="text-[var(--theme-text)] font-mono">{fileToDelete.filename}</strong>? This action cannot be undone and will permanently remove the item from Spotix.
153
  </p>
154
  <div className="flex gap-3 !cursor-none">
155
  <button
frontend/components/shared/FeedbackModal.tsx CHANGED
@@ -11,7 +11,7 @@ export default function FeedbackModal({ isOpen, onClose }: { isOpen: boolean, on
11
  const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
12
  const [errorMsg, setErrorMsg] = useState("");
13
 
14
- const handleSubmit = async (e: React.FormEvent) => {
15
  e.preventDefault();
16
  if (message.trim().length < 5) {
17
  setErrorMsg("Message must be at least 5 characters.");
 
11
  const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
12
  const [errorMsg, setErrorMsg] = useState("");
13
 
14
+ const handleSubmit = async (e: React.SubmitEvent) => {
15
  e.preventDefault();
16
  if (message.trim().length < 5) {
17
  setErrorMsg("Message must be at least 5 characters.");
frontend/components/shared/Navbar.tsx CHANGED
@@ -12,7 +12,7 @@ interface NavbarProps {
12
  export default function Navbar({ onAnalyzeClick }: NavbarProps) {
13
  const router = useRouter();
14
  const pathname = usePathname();
15
- const { isAuthenticated, user, logout } = useAuth();
16
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
17
  const [dropdownOpen, setDropdownOpen] = useState(false);
18
 
@@ -69,6 +69,7 @@ export default function Navbar({ onAnalyzeClick }: NavbarProps) {
69
  setLocalAvatar(base64String);
70
  try {
71
  await apiLayer.updateAvatar(base64String);
 
72
  } catch (err) {
73
  console.error("Failed to update avatar:", err);
74
  }
@@ -84,6 +85,7 @@ export default function Navbar({ onAnalyzeClick }: NavbarProps) {
84
  }
85
  try {
86
  await apiLayer.updateAvatar("");
 
87
  } catch (err) {
88
  console.error("Failed to remove avatar:", err);
89
  }
@@ -94,7 +96,7 @@ export default function Navbar({ onAnalyzeClick }: NavbarProps) {
94
  <>
95
  <nav className="fixed top-6 left-1/2 -translate-x-1/2 z-[60] w-[92%] max-w-5xl">
96
  <div className="bg-[var(--theme-bg)]/60 backdrop-blur-xl border border-[#4d453e]/15 rounded-full px-6 md:px-8 py-3 md:py-4 flex justify-between items-center shadow-[0_20px_50px_rgba(0,0,0,0.4)] transition-all duration-500">
97
- <div onClick={() => router.push('/')} className="cursor-pointer text-base md:text-lg font-black tracking-tighter text-[var(--theme-text)] uppercase hover:opacity-80 transition-opacity">OBSIDIAN</div>
98
 
99
  <div className="hidden lg:flex items-center gap-10">
100
  <a className={`font-['Inter'] tracking-tight font-medium text-[11px] uppercase transition-colors nav-link ${pathname === '/' ? 'text-[var(--theme-text)]' : 'text-[#d0c4bb] hover:text-[var(--theme-text)]'}`} href="/">Platform</a>
 
12
  export default function Navbar({ onAnalyzeClick }: NavbarProps) {
13
  const router = useRouter();
14
  const pathname = usePathname();
15
+ const { isAuthenticated, user, logout, updateUser } = useAuth();
16
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
17
  const [dropdownOpen, setDropdownOpen] = useState(false);
18
 
 
69
  setLocalAvatar(base64String);
70
  try {
71
  await apiLayer.updateAvatar(base64String);
72
+ updateUser({ avatar_url: base64String });
73
  } catch (err) {
74
  console.error("Failed to update avatar:", err);
75
  }
 
85
  }
86
  try {
87
  await apiLayer.updateAvatar("");
88
+ updateUser({ avatar_url: null });
89
  } catch (err) {
90
  console.error("Failed to remove avatar:", err);
91
  }
 
96
  <>
97
  <nav className="fixed top-6 left-1/2 -translate-x-1/2 z-[60] w-[92%] max-w-5xl">
98
  <div className="bg-[var(--theme-bg)]/60 backdrop-blur-xl border border-[#4d453e]/15 rounded-full px-6 md:px-8 py-3 md:py-4 flex justify-between items-center shadow-[0_20px_50px_rgba(0,0,0,0.4)] transition-all duration-500">
99
+ <div onClick={() => router.push('/')} className="cursor-pointer text-base md:text-lg font-black tracking-tighter text-[var(--theme-text)] uppercase hover:opacity-80 transition-opacity">SPOTIX</div>
100
 
101
  <div className="hidden lg:flex items-center gap-10">
102
  <a className={`font-['Inter'] tracking-tight font-medium text-[11px] uppercase transition-colors nav-link ${pathname === '/' ? 'text-[var(--theme-text)]' : 'text-[#d0c4bb] hover:text-[var(--theme-text)]'}`} href="/">Platform</a>
frontend/components/upload/UploadZone.tsx CHANGED
@@ -149,8 +149,7 @@ export default function UploadZone({ autoAnalyze = false }: { autoAnalyze?: bool
149
  </defs>
150
  </svg>
151
 
152
- <div
153
- className={`rounded-[2.5rem] p-1 md:p-2 bg-[var(--theme-bg)] backdrop-blur-3xl border-2 border-dashed border-[var(--theme-text)]/40 relative overflow-hidden group/upload transition-all duration-700 shadow-[0_40px_100px_rgba(0,0,0,0.5)] hover:border-[var(--theme-text)] hover:shadow-[0_0_40px_var(--theme-text)] ${isDragging ? 'border-[var(--theme-text)] shadow-[0_0_60px_var(--theme-text)]' : ''}`}
154
  onDragOver={onDragOver}
155
  onDragLeave={onDragLeave}
156
  onDrop={onDrop}
 
149
  </defs>
150
  </svg>
151
 
152
+ <div className={`rounded-[2.5rem] p-1 md:p-2 bg-[var(--theme-bg)] backdrop-blur-3xl border-2 border-dashed border-[var(--theme-text)]/40 relative overflow-hidden group/upload transition-all duration-700 hover:border-[var(--theme-text)] ${isDragging ? 'border-[var(--theme-text)]' : ''}`}
 
153
  onDragOver={onDragOver}
154
  onDragLeave={onDragLeave}
155
  onDrop={onDrop}
frontend/contexts/AuthContext.tsx CHANGED
@@ -9,6 +9,8 @@ type UserData = {
9
  name: string;
10
  is_admin: boolean;
11
  avatar_url?: string | null;
 
 
12
  };
13
 
14
  type AuthContextType = {
@@ -16,9 +18,10 @@ type AuthContextType = {
16
  user: UserData | null;
17
  loading: boolean;
18
  logout: () => void;
 
19
  };
20
 
21
- const AuthContext = createContext<AuthContextType>({ isAuthenticated: false, user: null, loading: true, logout: () => {} });
22
 
23
  export function AuthProvider({ children }: { children: React.ReactNode }) {
24
  const [isAuthenticated, setIsAuthenticated] = useState(false);
@@ -58,8 +61,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
58
  router.push("/login");
59
  };
60
 
 
 
 
 
61
  return (
62
- <AuthContext.Provider value={{ isAuthenticated, user, loading, logout }}>
63
  {children}
64
  </AuthContext.Provider>
65
  );
 
9
  name: string;
10
  is_admin: boolean;
11
  avatar_url?: string | null;
12
+ google_avatar_url?: string | null;
13
+ created_at?: string | null;
14
  };
15
 
16
  type AuthContextType = {
 
18
  user: UserData | null;
19
  loading: boolean;
20
  logout: () => void;
21
+ updateUser: (data: Partial<UserData>) => void;
22
  };
23
 
24
+ const AuthContext = createContext<AuthContextType>({ isAuthenticated: false, user: null, loading: true, logout: () => {}, updateUser: () => {} });
25
 
26
  export function AuthProvider({ children }: { children: React.ReactNode }) {
27
  const [isAuthenticated, setIsAuthenticated] = useState(false);
 
61
  router.push("/login");
62
  };
63
 
64
+ const updateUser = (data: Partial<UserData>) => {
65
+ setUser(prev => prev ? { ...prev, ...data } : null);
66
+ };
67
+
68
  return (
69
+ <AuthContext.Provider value={{ isAuthenticated, user, loading, logout, updateUser }}>
70
  {children}
71
  </AuthContext.Provider>
72
  );