muhammadpriv001 commited on
Commit
4ed93e5
·
1 Parent(s): 4346a4c

Home Page

Browse files
frontend/app/globals.css CHANGED
@@ -29,10 +29,9 @@ body {
29
 
30
  a { color: inherit; text-decoration: none; }
31
 
32
- ::-webkit-scrollbar { width: 8px; height: 8px; }
33
- ::-webkit-scrollbar-track { background: #e0e5ec; }
34
- ::-webkit-scrollbar-thumb { background: #b8bec7; border-radius: 4px; }
35
- ::-webkit-scrollbar-thumb:hover { background: #a0a5ad; }
36
 
37
  /* ===== NEUMORPHISM ===== */
38
  .neu-card {
@@ -282,7 +281,7 @@ a { color: inherit; text-decoration: none; }
282
  display: block;
283
  width: 22px;
284
  height: 2px;
285
- background: #f1f5f9;
286
  border-radius: 2px;
287
  transition: all 0.3s ease;
288
  }
 
29
 
30
  a { color: inherit; text-decoration: none; }
31
 
32
+ /* Hide scrollbars globally */
33
+ ::-webkit-scrollbar { display: none; width: 0; height: 0; }
34
+ * { scrollbar-width: none; -ms-overflow-style: none; }
 
35
 
36
  /* ===== NEUMORPHISM ===== */
37
  .neu-card {
 
281
  display: block;
282
  width: 22px;
283
  height: 2px;
284
+ background: #4a5568;
285
  border-radius: 2px;
286
  transition: all 0.3s ease;
287
  }
frontend/app/page.tsx CHANGED
@@ -71,23 +71,17 @@ export default function Home() {
71
  {mounted && <ParallaxScene />}
72
 
73
  <div style={{ position: "relative", zIndex: 1, textAlign: "center", maxWidth: "720px" }}>
74
- <div className="anim-fade-up delay-1" style={{ marginBottom: "16px" }}>
75
- <span className="badge-blue" style={{ padding: "6px 16px", borderRadius: "9999px", fontSize: "0.7rem", fontWeight: 700, letterSpacing: "0.1em" }}>
76
- NEW PLATFORM
77
- </span>
78
- </div>
79
-
80
- <h1 className="anim-fade-up delay-2" style={{ fontSize: "clamp(2rem, 5vw, 3.2rem)", fontWeight: 900, color: "#f1f5f9", lineHeight: 1.1, marginBottom: "18px", letterSpacing: "-0.02em" }}>
81
  OBJECT
82
  <span style={{ display: "block", color: "#1E90FF" }}>INTELLIGENCE</span>
83
  </h1>
84
 
85
- <p className="anim-fade-up delay-3" style={{ color: "#94a3b8", fontSize: "1rem", lineHeight: 1.7, maxWidth: "560px", margin: "0 auto 32px" }}>
86
  Real-time multi-pipeline AI detection and recognition platform.
87
  Powered by RT-DETR, YOLO-World, and visual embeddings.
88
  </p>
89
 
90
- <div className="anim-fade-up delay-4" style={{ display: "flex", gap: "12px", justifyContent: "center", flexWrap: "wrap" }}>
91
  <Link href="/live" className="neu-btn-primary" style={{ padding: "14px 28px", fontSize: "0.85rem" }}>
92
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
93
  Live Testing
@@ -103,11 +97,14 @@ export default function Home() {
103
  {/* ===== SCROLLING BANNER ===== */}
104
  <div
105
  style={{
106
- background: "linear-gradient(90deg, var(--bg-dark) 0%, #0f1729 50%, var(--bg-dark) 100%)",
107
- borderTop: "1px solid rgba(30, 144, 255, 0.1)",
108
- borderBottom: "1px solid rgba(30, 144, 255, 0.1)",
109
- overflow: "hidden",
 
110
  padding: "14px 0",
 
 
111
  }}
112
  >
113
  <div
@@ -125,7 +122,7 @@ export default function Home() {
125
  fontSize: "0.75rem",
126
  fontWeight: 700,
127
  letterSpacing: "0.15em",
128
- color: "rgba(30, 144, 255, 0.5)",
129
  whiteSpace: "nowrap",
130
  display: "flex",
131
  alignItems: "center",
@@ -133,7 +130,7 @@ export default function Home() {
133
  }}
134
  >
135
  {item}
136
- <span style={{ width: "4px", height: "4px", borderRadius: "50%", background: "rgba(30, 144, 255, 0.3)" }} />
137
  </span>
138
  ))}
139
  </div>
@@ -145,6 +142,9 @@ export default function Home() {
145
  `}</style>
146
  </div>
147
 
 
 
 
148
  {/* ===== PIPELINE CARDS ===== */}
149
  <section style={{ maxWidth: "1200px", margin: "0 auto", padding: "80px 24px" }}>
150
  <div style={{ textAlign: "center", marginBottom: "48px" }}>
@@ -203,14 +203,17 @@ export default function Home() {
203
  </div>
204
  </section>
205
 
 
 
 
206
  {/* ===== AI PIPELINE ===== */}
207
- <section style={{ background: "var(--bg-dark)", padding: "80px 24px" }}>
208
  <div style={{ maxWidth: "1200px", margin: "0 auto" }}>
209
  <div style={{ textAlign: "center", marginBottom: "48px" }}>
210
  <p className="anim-fade-up" style={{ fontSize: "0.7rem", fontWeight: 700, letterSpacing: "0.2em", color: "#1E90FF", marginBottom: "8px", textTransform: "uppercase" }}>
211
  How It Works
212
  </p>
213
- <h2 className="anim-fade-up delay-1" style={{ fontSize: "clamp(1.5rem, 3vw, 2rem)", fontWeight: 800, color: "#f1f5f9" }}>
214
  AI Processing Pipeline
215
  </h2>
216
  </div>
@@ -219,7 +222,7 @@ export default function Home() {
219
  {STEPS.map((s, i) => (
220
  <div
221
  key={s.num}
222
- className={`neu-card-dark anim-scale-in delay-${i + 1}`}
223
  style={{ padding: "28px 20px", textAlign: "center", position: "relative" }}
224
  >
225
  <div
@@ -237,7 +240,7 @@ export default function Home() {
237
  <span style={{ fontSize: "0.6rem", fontWeight: 700, letterSpacing: "0.15em", color: "#1E90FF", display: "block", marginBottom: "10px" }}>
238
  {s.num}
239
  </span>
240
- <h4 style={{ fontSize: "0.8rem", fontWeight: 700, color: "#f1f5f9", marginBottom: "6px", letterSpacing: "0.08em" }}>
241
  {s.title}
242
  </h4>
243
  <p style={{ fontSize: "0.7rem", color: "#64748b" }}>{s.desc}</p>
@@ -247,6 +250,9 @@ export default function Home() {
247
  </div>
248
  </section>
249
 
 
 
 
250
  {/* ===== LOCK MODE PREVIEW ===== */}
251
  <section style={{ maxWidth: "1200px", margin: "0 auto", padding: "80px 24px" }}>
252
  <div
@@ -339,40 +345,43 @@ export default function Home() {
339
  </section>
340
 
341
  {/* ===== BOTTOM CTA ===== */}
342
- <section style={{ background: "var(--bg-dark)", padding: "80px 24px" }}>
343
- <div style={{ maxWidth: "700px", margin: "0 auto", textAlign: "center" }}>
344
- <h2 className="anim-fade-up" style={{ fontSize: "clamp(1.5rem, 3vw, 2rem)", fontWeight: 800, color: "#f1f5f9", marginBottom: "12px" }}>
345
- Start Detecting
346
- </h2>
347
- <p className="anim-fade-up delay-1" style={{ color: "#94a3b8", fontSize: "0.9rem", marginBottom: "32px" }}>
348
- Launch real-time detection in your browser.
349
- </p>
 
 
350
 
351
- <div className="anim-fade-up delay-2" style={{ display: "flex", gap: "16px", justifyContent: "center", flexWrap: "wrap" }}>
352
- <Link
353
- href="/live"
354
- className="neu-btn-primary"
355
- style={{ padding: "16px 32px", fontSize: "0.9rem", borderRadius: "14px" }}
356
- >
357
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
358
- Live Testing
359
- </Link>
360
- <Link
361
- href="/objects"
362
- className="neu-btn-primary"
363
- style={{ padding: "16px 32px", fontSize: "0.9rem", borderRadius: "14px" }}
364
- >
365
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
366
- Object Studio
367
- </Link>
368
- <Link
369
- href="/guides"
370
- className="neu-btn-primary"
371
- style={{ padding: "16px 32px", fontSize: "0.9rem", borderRadius: "14px" }}
372
- >
373
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
374
- Read Guides
375
- </Link>
 
376
  </div>
377
  </div>
378
  </section>
 
71
  {mounted && <ParallaxScene />}
72
 
73
  <div style={{ position: "relative", zIndex: 1, textAlign: "center", maxWidth: "720px" }}>
74
+ <h1 className="anim-fade-up delay-1" style={{ fontSize: "clamp(2rem, 5vw, 3.2rem)", fontWeight: 900, color: "#f1f5f9", lineHeight: 1.1, marginBottom: "18px", letterSpacing: "-0.02em" }}>
 
 
 
 
 
 
75
  OBJECT
76
  <span style={{ display: "block", color: "#1E90FF" }}>INTELLIGENCE</span>
77
  </h1>
78
 
79
+ <p className="anim-fade-up delay-2" style={{ color: "#94a3b8", fontSize: "1rem", lineHeight: 1.7, maxWidth: "560px", margin: "0 auto 32px" }}>
80
  Real-time multi-pipeline AI detection and recognition platform.
81
  Powered by RT-DETR, YOLO-World, and visual embeddings.
82
  </p>
83
 
84
+ <div className="anim-fade-up delay-3" style={{ display: "flex", gap: "12px", justifyContent: "center", flexWrap: "wrap" }}>
85
  <Link href="/live" className="neu-btn-primary" style={{ padding: "14px 28px", fontSize: "0.85rem" }}>
86
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
87
  Live Testing
 
97
  {/* ===== SCROLLING BANNER ===== */}
98
  <div
99
  style={{
100
+ background: "#0a0e17",
101
+ borderTop: "2px solid #1E90FF",
102
+ borderBottom: "3px solid #1E90FF",
103
+ boxShadow: "0 4px 24px rgba(30, 144, 255, 0.25)",
104
+ overflow: "visible",
105
  padding: "14px 0",
106
+ position: "relative",
107
+ zIndex: 5,
108
  }}
109
  >
110
  <div
 
122
  fontSize: "0.75rem",
123
  fontWeight: 700,
124
  letterSpacing: "0.15em",
125
+ color: "#1E90FF",
126
  whiteSpace: "nowrap",
127
  display: "flex",
128
  alignItems: "center",
 
130
  }}
131
  >
132
  {item}
133
+ <span style={{ width: "4px", height: "4px", borderRadius: "50%", background: "#64B5F6" }} />
134
  </span>
135
  ))}
136
  </div>
 
142
  `}</style>
143
  </div>
144
 
145
+ {/* Divider */}
146
+ <div style={{ height: "2px", background: "linear-gradient(90deg, transparent 0%, #1E90FF 20%, #1E90FF 80%, transparent 100%)", margin: "0 auto", maxWidth: "400px", opacity: 0.2 }} />
147
+
148
  {/* ===== PIPELINE CARDS ===== */}
149
  <section style={{ maxWidth: "1200px", margin: "0 auto", padding: "80px 24px" }}>
150
  <div style={{ textAlign: "center", marginBottom: "48px" }}>
 
203
  </div>
204
  </section>
205
 
206
+ {/* Divider */}
207
+ <div style={{ height: "2px", background: "linear-gradient(90deg, transparent 0%, #1E90FF 20%, #1E90FF 80%, transparent 100%)", margin: "0 auto", maxWidth: "400px", opacity: 0.2 }} />
208
+
209
  {/* ===== AI PIPELINE ===== */}
210
+ <section style={{ background: "linear-gradient(180deg, #e8edf5 0%, #dbe4f0 100%)", padding: "80px 24px" }}>
211
  <div style={{ maxWidth: "1200px", margin: "0 auto" }}>
212
  <div style={{ textAlign: "center", marginBottom: "48px" }}>
213
  <p className="anim-fade-up" style={{ fontSize: "0.7rem", fontWeight: 700, letterSpacing: "0.2em", color: "#1E90FF", marginBottom: "8px", textTransform: "uppercase" }}>
214
  How It Works
215
  </p>
216
+ <h2 className="anim-fade-up delay-1" style={{ fontSize: "clamp(1.5rem, 3vw, 2rem)", fontWeight: 800, color: "#1a202c" }}>
217
  AI Processing Pipeline
218
  </h2>
219
  </div>
 
222
  {STEPS.map((s, i) => (
223
  <div
224
  key={s.num}
225
+ className={`neu-card anim-scale-in delay-${i + 1}`}
226
  style={{ padding: "28px 20px", textAlign: "center", position: "relative" }}
227
  >
228
  <div
 
240
  <span style={{ fontSize: "0.6rem", fontWeight: 700, letterSpacing: "0.15em", color: "#1E90FF", display: "block", marginBottom: "10px" }}>
241
  {s.num}
242
  </span>
243
+ <h4 style={{ fontSize: "0.8rem", fontWeight: 700, color: "#1a202c", marginBottom: "6px", letterSpacing: "0.08em" }}>
244
  {s.title}
245
  </h4>
246
  <p style={{ fontSize: "0.7rem", color: "#64748b" }}>{s.desc}</p>
 
250
  </div>
251
  </section>
252
 
253
+ {/* Divider */}
254
+ <div style={{ height: "2px", background: "linear-gradient(90deg, transparent 0%, #1E90FF 20%, #1E90FF 80%, transparent 100%)", margin: "0 auto", maxWidth: "400px", opacity: 0.2 }} />
255
+
256
  {/* ===== LOCK MODE PREVIEW ===== */}
257
  <section style={{ maxWidth: "1200px", margin: "0 auto", padding: "80px 24px" }}>
258
  <div
 
345
  </section>
346
 
347
  {/* ===== BOTTOM CTA ===== */}
348
+ <div style={{ height: "2px", background: "linear-gradient(90deg, transparent 0%, #1E90FF 20%, #1E90FF 80%, transparent 100%)", margin: "0 auto", maxWidth: "400px", opacity: 0.2 }} />
349
+ <section style={{ background: "linear-gradient(180deg, #e8edf5 0%, #dbe4f0 100%)", padding: "80px 24px" }}>
350
+ <div style={{ maxWidth: "700px", margin: "0 auto" }}>
351
+ <div className="neu-card-dark" style={{ padding: "48px 40px", textAlign: "center", border: "1px solid rgba(30, 144, 255, 0.15)" }}>
352
+ <h2 className="anim-fade-up" style={{ fontSize: "clamp(1.5rem, 3vw, 2rem)", fontWeight: 800, color: "#f1f5f9", marginBottom: "12px" }}>
353
+ Start Detecting
354
+ </h2>
355
+ <p className="anim-fade-up delay-1" style={{ color: "#94a3b8", fontSize: "0.9rem", marginBottom: "32px" }}>
356
+ Launch real-time detection in your browser.
357
+ </p>
358
 
359
+ <div className="anim-fade-up delay-2" style={{ display: "flex", gap: "16px", justifyContent: "center", flexWrap: "wrap" }}>
360
+ <Link
361
+ href="/live"
362
+ className="neu-btn-primary"
363
+ style={{ padding: "16px 32px", fontSize: "0.9rem", borderRadius: "14px" }}
364
+ >
365
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
366
+ Live Testing
367
+ </Link>
368
+ <Link
369
+ href="/objects"
370
+ className="neu-btn-primary"
371
+ style={{ padding: "16px 32px", fontSize: "0.9rem", borderRadius: "14px" }}
372
+ >
373
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
374
+ Object Studio
375
+ </Link>
376
+ <Link
377
+ href="/guides"
378
+ className="neu-btn-primary"
379
+ style={{ padding: "16px 32px", fontSize: "0.9rem", borderRadius: "14px" }}
380
+ >
381
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
382
+ Read Guides
383
+ </Link>
384
+ </div>
385
  </div>
386
  </div>
387
  </section>
frontend/components/Footer.tsx CHANGED
@@ -26,11 +26,13 @@ export default function Footer() {
26
  top: "50%",
27
  left: "50%",
28
  transform: "translate(-50%, -50%)",
29
- whiteSpace: "nowrap",
30
- fontSize: "clamp(3rem, 10vw, 8rem)",
 
 
31
  fontWeight: 900,
32
- letterSpacing: "0.06em",
33
- color: "rgba(30, 144, 255, 0.04)",
34
  userSelect: "none",
35
  pointerEvents: "none",
36
  lineHeight: 1,
 
26
  top: "50%",
27
  left: "50%",
28
  transform: "translate(-50%, -50%)",
29
+ width: "1400px",
30
+ maxWidth: "calc(100% - 48px)",
31
+ textAlign: "center",
32
+ fontSize: "clamp(2.5rem, 8vw, 7rem)",
33
  fontWeight: 900,
34
+ letterSpacing: "0.08em",
35
+ color: "rgba(30, 144, 255, 0.035)",
36
  userSelect: "none",
37
  pointerEvents: "none",
38
  lineHeight: 1,
frontend/components/Navbar.tsx CHANGED
@@ -1,6 +1,7 @@
1
  "use client";
2
 
3
  import Link from "next/link";
 
4
  import { useState, useEffect } from "react";
5
 
6
  const NAV_LINKS = [
@@ -12,20 +13,9 @@ const NAV_LINKS = [
12
  ];
13
 
14
  export default function Navbar() {
15
- const [online, setOnline] = useState(false);
16
  const [menuOpen, setMenuOpen] = useState(false);
17
 
18
- useEffect(() => {
19
- const check = () =>
20
- fetch("/backend-api/health", { method: "GET" })
21
- .then((r) => r.ok)
22
- .then(setOnline)
23
- .catch(() => setOnline(false));
24
- check();
25
- const id = setInterval(check, 8000);
26
- return () => clearInterval(id);
27
- }, []);
28
-
29
  useEffect(() => {
30
  if (menuOpen) {
31
  document.body.style.overflow = "hidden";
@@ -42,9 +32,9 @@ export default function Navbar() {
42
  position: "sticky",
43
  top: 0,
44
  zIndex: 100,
45
- background: "rgba(10, 14, 23, 0.92)",
46
- backdropFilter: "blur(16px)",
47
- borderBottom: "1px solid rgba(30, 144, 255, 0.15)",
48
  padding: "0 24px",
49
  height: "60px",
50
  display: "flex",
@@ -81,55 +71,47 @@ export default function Navbar() {
81
  <path d="M2 12l10 5 10-5" />
82
  </svg>
83
  </div>
84
- <span style={{ color: "#f1f5f9", fontWeight: 800, fontSize: "0.95rem", letterSpacing: "0.08em" }}>
85
  OBJECT INTELLIGENCE
86
  </span>
87
  </Link>
88
 
89
  {/* Desktop nav right */}
90
  <div className="desktop-nav" style={{ display: "flex", alignItems: "center", gap: "4px" }}>
91
- {NAV_LINKS.map((l) => (
92
- <Link
93
- key={l.href}
94
- href={l.href}
95
- style={{
96
- padding: "8px 14px",
97
- fontSize: "0.75rem",
98
- fontWeight: 700,
99
- letterSpacing: "0.1em",
100
- color: "#94a3b8",
101
- borderRadius: "8px",
102
- transition: "all 0.2s ease",
103
- }}
104
- onMouseEnter={(e) => {
105
- e.currentTarget.style.color = "#1E90FF";
106
- e.currentTarget.style.background = "rgba(30, 144, 255, 0.08)";
107
- }}
108
- onMouseLeave={(e) => {
109
- e.currentTarget.style.color = "#94a3b8";
110
- e.currentTarget.style.background = "transparent";
111
- }}
112
- >
113
- {l.label}
114
- </Link>
115
- ))}
116
-
117
- {/* Status dot */}
118
- <div style={{ display: "flex", alignItems: "center", gap: "6px", marginLeft: "12px", paddingLeft: "12px", borderLeft: "1px solid rgba(255,255,255,0.08)" }}>
119
- <div
120
- style={{
121
- width: "7px",
122
- height: "7px",
123
- borderRadius: "50%",
124
- background: online ? "#10b981" : "#ef4444",
125
- boxShadow: online ? "0 0 8px rgba(16, 185, 129, 0.6)" : "0 0 8px rgba(239, 68, 68, 0.6)",
126
- animation: online ? "none" : "none",
127
- }}
128
- />
129
- <span style={{ fontSize: "0.65rem", color: "#64748b", fontWeight: 600, letterSpacing: "0.05em" }}>
130
- {online ? "ONLINE" : "OFFLINE"}
131
- </span>
132
- </div>
133
  </div>
134
 
135
  {/* Mobile hamburger */}
 
1
  "use client";
2
 
3
  import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
  import { useState, useEffect } from "react";
6
 
7
  const NAV_LINKS = [
 
13
  ];
14
 
15
  export default function Navbar() {
16
+ const pathname = usePathname();
17
  const [menuOpen, setMenuOpen] = useState(false);
18
 
 
 
 
 
 
 
 
 
 
 
 
19
  useEffect(() => {
20
  if (menuOpen) {
21
  document.body.style.overflow = "hidden";
 
32
  position: "sticky",
33
  top: 0,
34
  zIndex: 100,
35
+ background: "rgba(30, 144, 255, 0.06)",
36
+ backdropFilter: "blur(12px)",
37
+ borderBottom: "1px solid rgba(30, 144, 255, 0.1)",
38
  padding: "0 24px",
39
  height: "60px",
40
  display: "flex",
 
71
  <path d="M2 12l10 5 10-5" />
72
  </svg>
73
  </div>
74
+ <span style={{ color: "#1a202c", fontWeight: 800, fontSize: "0.95rem", letterSpacing: "0.08em" }}>
75
  OBJECT INTELLIGENCE
76
  </span>
77
  </Link>
78
 
79
  {/* Desktop nav right */}
80
  <div className="desktop-nav" style={{ display: "flex", alignItems: "center", gap: "4px" }}>
81
+ {NAV_LINKS.map((l) => {
82
+ const isActive = l.href === "/" ? pathname === "/" : pathname.startsWith(l.href);
83
+ return (
84
+ <Link
85
+ key={l.href}
86
+ href={l.href}
87
+ style={{
88
+ padding: "8px 14px",
89
+ fontSize: "0.75rem",
90
+ fontWeight: 700,
91
+ letterSpacing: "0.1em",
92
+ color: isActive ? "#1E90FF" : "#64748b",
93
+ borderRadius: "10px",
94
+ transition: "all 0.2s ease",
95
+ background: isActive ? "rgba(30, 144, 255, 0.1)" : "transparent",
96
+ boxShadow: isActive ? "inset 2px 2px 4px #b8bec7, inset -2px -2px 4px #ffffff" : "none",
97
+ }}
98
+ onMouseEnter={(e) => {
99
+ if (!isActive) {
100
+ e.currentTarget.style.color = "#1E90FF";
101
+ e.currentTarget.style.background = "rgba(30, 144, 255, 0.06)";
102
+ }
103
+ }}
104
+ onMouseLeave={(e) => {
105
+ if (!isActive) {
106
+ e.currentTarget.style.color = "#64748b";
107
+ e.currentTarget.style.background = "transparent";
108
+ }
109
+ }}
110
+ >
111
+ {l.label}
112
+ </Link>
113
+ );
114
+ })}
 
 
 
 
 
 
 
 
115
  </div>
116
 
117
  {/* Mobile hamburger */}
frontend/components/ParallaxScene.tsx CHANGED
@@ -3,6 +3,22 @@
3
  import { useEffect, useRef } from "react";
4
  import * as THREE from "three";
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  export default function ParallaxScene() {
7
  const mountRef = useRef<HTMLDivElement>(null);
8
  const mouseRef = useRef({ x: 0, y: 0 });
@@ -21,50 +37,178 @@ export default function ParallaxScene() {
21
  renderer.setClearColor(0x000000, 0);
22
  el.appendChild(renderer.domElement);
23
 
24
- const blueMat = new THREE.MeshBasicMaterial({ color: 0x1e90ff, wireframe: true, transparent: true, opacity: 0.12 });
25
- const blueMatSolid = new THREE.MeshBasicMaterial({ color: 0x1e90ff, transparent: true, opacity: 0.06 });
26
- const accentMat = new THREE.MeshBasicMaterial({ color: 0x64b5f6, wireframe: true, transparent: true, opacity: 0.08 });
 
 
 
27
 
28
- const shapes: { mesh: THREE.Mesh; speed: number; rotSpeed: number }[] = [];
29
 
30
- const addShape = (geo: THREE.BufferGeometry, mat: THREE.Material, x: number, y: number, z: number, speed: number, rotSpeed: number) => {
31
- const mesh = new THREE.Mesh(geo, mat);
32
  mesh.position.set(x, y, z);
 
33
  scene.add(mesh);
34
- shapes.push({ mesh, speed, rotSpeed });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  };
36
 
37
- // Background layer - large slow shapes
38
- addShape(new THREE.IcosahedronGeometry(4, 0), blueMatSolid, -12, 8, -15, 0.3, 0.1);
39
- addShape(new THREE.OctahedronGeometry(3, 0), blueMat, 14, -6, -12, 0.2, 0.15);
40
- addShape(new THREE.TetrahedronGeometry(2.5, 0), accentMat, -8, -10, -10, 0.25, 0.2);
41
- addShape(new THREE.IcosahedronGeometry(2, 1), blueMat, 10, 10, -8, 0.35, 0.12);
42
- addShape(new THREE.BoxGeometry(3, 3, 3), blueMatSolid, 0, -8, -14, 0.15, 0.08);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
- // Middle layer
45
- addShape(new THREE.OctahedronGeometry(1.8, 0), blueMat, -6, 4, -4, 0.5, 0.2);
46
- addShape(new THREE.TetrahedronGeometry(1.5, 0), accentMat, 8, 2, -3, 0.6, 0.25);
47
- addShape(new THREE.IcosahedronGeometry(1.2, 0), blueMatSolid, -3, -4, -2, 0.4, 0.18);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- // Front layer - small fast shapes
50
- addShape(new THREE.BoxGeometry(0.8, 0.8, 0.8), blueMat, 5, -3, 5, 0.8, 0.4);
51
- addShape(new THREE.OctahedronGeometry(0.6, 0), accentMat, -7, 6, 4, 0.9, 0.35);
52
- addShape(new THREE.TetrahedronGeometry(0.5, 0), blueMatSolid, 3, 8, 6, 1.0, 0.5);
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- // Particles
55
  const particleGeo = new THREE.BufferGeometry();
56
- const particleCount = 60;
57
  const positions = new Float32Array(particleCount * 3);
 
58
  for (let i = 0; i < particleCount; i++) {
59
- positions[i * 3] = (Math.random() - 0.5) * 50;
60
- positions[i * 3 + 1] = (Math.random() - 0.5) * 30;
61
- positions[i * 3 + 2] = (Math.random() - 0.5) * 30 - 5;
 
62
  }
63
  particleGeo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
64
- const particleMat = new THREE.PointsMaterial({ color: 0x1e90ff, size: 0.1, transparent: true, opacity: 0.4 });
 
65
  const particles = new THREE.Points(particleGeo, particleMat);
66
  scene.add(particles);
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  const onMouseMove = (e: MouseEvent) => {
69
  const rect = el.getBoundingClientRect();
70
  mouseRef.current.x = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
@@ -72,28 +216,75 @@ export default function ParallaxScene() {
72
  };
73
  window.addEventListener("mousemove", onMouseMove);
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  let raf: number;
76
  const animate = () => {
77
  raf = requestAnimationFrame(animate);
78
- const t = Date.now() * 0.001;
79
  const mx = mouseRef.current.x;
80
  const my = mouseRef.current.y;
 
81
 
82
  shapes.forEach((s) => {
83
- s.mesh.rotation.x += s.rotSpeed * 0.008;
84
- s.mesh.rotation.y += s.rotSpeed * 0.012;
85
- // Parallax offset based on mouse
86
- s.mesh.position.x += (s.mesh.position.x * 0.0001 + mx * s.speed * 0.01 - (s.mesh.position.x - s.mesh.position.x)) * 0.02;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  });
88
 
89
- // Parallax camera
90
  camera.position.x += (mx * 2 - camera.position.x) * 0.03;
91
  camera.position.y += (-my * 1.5 - camera.position.y) * 0.03;
92
  camera.lookAt(0, 0, 0);
93
 
94
- particles.rotation.y += 0.0005;
95
  particles.rotation.x += 0.0002;
96
 
 
 
 
 
 
 
97
  renderer.render(scene, camera);
98
  };
99
  animate();
@@ -110,6 +301,7 @@ export default function ParallaxScene() {
110
  cancelAnimationFrame(raf);
111
  window.removeEventListener("mousemove", onMouseMove);
112
  window.removeEventListener("resize", onResize);
 
113
  el.removeChild(renderer.domElement);
114
  renderer.dispose();
115
  };
@@ -122,7 +314,6 @@ export default function ParallaxScene() {
122
  position: "absolute",
123
  inset: 0,
124
  zIndex: 0,
125
- pointerEvents: "none",
126
  }}
127
  />
128
  );
 
3
  import { useEffect, useRef } from "react";
4
  import * as THREE from "three";
5
 
6
+ interface Shape {
7
+ mesh: THREE.Mesh;
8
+ vx: number;
9
+ vy: number;
10
+ vz: number;
11
+ rvx: number;
12
+ rvy: number;
13
+ baseX: number;
14
+ baseY: number;
15
+ baseZ: number;
16
+ speed: number;
17
+ scatterX: number;
18
+ scatterY: number;
19
+ scatterZ: number;
20
+ }
21
+
22
  export default function ParallaxScene() {
23
  const mountRef = useRef<HTMLDivElement>(null);
24
  const mouseRef = useRef({ x: 0, y: 0 });
 
37
  renderer.setClearColor(0x000000, 0);
38
  el.appendChild(renderer.domElement);
39
 
40
+ const blueMat = new THREE.MeshBasicMaterial({ color: 0x1e90ff, wireframe: true, transparent: true, opacity: 0.15 });
41
+ const blueSolid = new THREE.MeshBasicMaterial({ color: 0x1e90ff, transparent: true, opacity: 0.06 });
42
+ const accentMat = new THREE.MeshBasicMaterial({ color: 0x64b5f6, wireframe: true, transparent: true, opacity: 0.1 });
43
+ const accentSolid = new THREE.MeshBasicMaterial({ color: 0x64b5f6, transparent: true, opacity: 0.04 });
44
+ const lineBlue = new THREE.LineBasicMaterial({ color: 0x1e90ff, transparent: true, opacity: 0.12 });
45
+ const lineAccent = new THREE.LineBasicMaterial({ color: 0x64b5f6, transparent: true, opacity: 0.08 });
46
 
47
+ const shapes: Shape[] = [];
48
 
49
+ const addShape = (geo: THREE.BufferGeometry, mat: THREE.Material | THREE.LineBasicMaterial, x: number, y: number, z: number, speed: number) => {
50
+ const mesh = "isLine" in mat ? new THREE.Line(geo as THREE.BufferGeometry, mat as THREE.LineBasicMaterial) : new THREE.Mesh(geo as THREE.BufferGeometry, mat as THREE.MeshBasicMaterial);
51
  mesh.position.set(x, y, z);
52
+ mesh.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
53
  scene.add(mesh);
54
+ shapes.push({
55
+ mesh: mesh as unknown as THREE.Mesh,
56
+ vx: (Math.random() - 0.5) * 0.008 * speed,
57
+ vy: (Math.random() - 0.5) * 0.006 * speed,
58
+ vz: (Math.random() - 0.5) * 0.004 * speed,
59
+ rvx: (Math.random() - 0.5) * 0.01 * speed,
60
+ rvy: (Math.random() - 0.5) * 0.012 * speed,
61
+ baseX: x,
62
+ baseY: y,
63
+ baseZ: z,
64
+ speed,
65
+ scatterX: 0,
66
+ scatterY: 0,
67
+ scatterZ: 0,
68
+ });
69
  };
70
 
71
+ const spread = 35;
72
+ const spreadY = 20;
73
+
74
+ // === LARGE BACKGROUND SHAPES (7) ===
75
+ addShape(new THREE.IcosahedronGeometry(5, 0), blueSolid, -spread, spreadY, -20, 0.3);
76
+ addShape(new THREE.OctahedronGeometry(4, 0), blueMat, spread, -spreadY * 0.8, -18, 0.25);
77
+ addShape(new THREE.TetrahedronGeometry(3.5, 0), accentMat, -spread * 0.7, -spreadY, -16, 0.35);
78
+ addShape(new THREE.IcosahedronGeometry(3, 1), blueMat, spread * 0.8, spreadY * 0.7, -14, 0.4);
79
+ addShape(new THREE.BoxGeometry(4, 4, 4), blueSolid, 0, -spreadY * 0.6, -22, 0.2);
80
+ addShape(new THREE.DodecahedronGeometry(3.5, 0), accentMat, -spread * 0.9, spreadY * 0.3, -17, 0.28);
81
+ addShape(new THREE.IcosahedronGeometry(2.5, 0), blueSolid, spread * 0.95, -spreadY * 0.4, -19, 0.32);
82
+
83
+ // === MID-LAYER SHAPES (14) ===
84
+ addShape(new THREE.OctahedronGeometry(2.2, 0), blueMat, -spread * 0.5, spreadY * 0.6, -6, 0.5);
85
+ addShape(new THREE.TetrahedronGeometry(2, 0), accentMat, spread * 0.4, spreadY * 0.3, -5, 0.55);
86
+ addShape(new THREE.IcosahedronGeometry(1.8, 0), blueSolid, -spread * 0.3, -spreadY * 0.5, -4, 0.45);
87
+ addShape(new THREE.BoxGeometry(1.8, 1.8, 1.8), accentMat, spread * 0.35, -spreadY * 0.4, -7, 0.48);
88
+ addShape(new THREE.DodecahedronGeometry(1.6, 0), blueMat, -spread * 0.75, spreadY * 0.15, -8, 0.52);
89
+ addShape(new THREE.OctahedronGeometry(1.4, 0), accentSolid, spread * 0.85, spreadY * 0.5, -6, 0.6);
90
+ addShape(new THREE.TetrahedronGeometry(1.2, 0), blueMat, -spread * 0.15, spreadY * 0.8, -5, 0.58);
91
+ addShape(new THREE.IcosahedronGeometry(1.5, 0), blueSolid, spread * 0.2, -spreadY * 0.7, -9, 0.42);
92
+ addShape(new THREE.BoxGeometry(1.4, 1.4, 1.4), accentMat, -spread * 0.6, -spreadY * 0.2, -7, 0.5);
93
+ addShape(new THREE.OctahedronGeometry(1.3, 0), blueMat, spread * 0.65, spreadY * 0.1, -4, 0.53);
94
+ addShape(new THREE.DodecahedronGeometry(1.1, 0), accentMat, -spread * 0.4, spreadY * 0.45, -8, 0.47);
95
+ addShape(new THREE.TetrahedronGeometry(1, 0), blueSolid, spread * 0.5, -spreadY * 0.55, -6, 0.56);
96
+ addShape(new THREE.IcosahedronGeometry(1.2, 0), blueMat, -spread * 0.25, -spreadY * 0.35, -5, 0.44);
97
+ addShape(new THREE.BoxGeometry(1, 1, 1), accentMat, spread * 0.15, spreadY * 0.65, -7, 0.51);
98
+
99
+ // === FRONT LAYER - SMALL FAST (18) ===
100
+ for (let i = 0; i < 18; i++) {
101
+ const geos = [
102
+ new THREE.BoxGeometry(0.5, 0.5, 0.5),
103
+ new THREE.OctahedronGeometry(0.4, 0),
104
+ new THREE.TetrahedronGeometry(0.35, 0),
105
+ new THREE.IcosahedronGeometry(0.3, 0),
106
+ new THREE.DodecahedronGeometry(0.35, 0),
107
+ ];
108
+ const mats = [blueMat, accentMat, blueSolid, accentSolid];
109
+ const x = (Math.random() - 0.5) * spread * 2;
110
+ const y = (Math.random() - 0.5) * spreadY * 2;
111
+ const z = Math.random() * 8 - 2;
112
+ addShape(geos[i % geos.length], mats[i % mats.length], x, y, z, 0.7 + Math.random() * 0.5);
113
+ }
114
+
115
+ // === TINY SCATTERED SHAPES (20) ===
116
+ for (let i = 0; i < 20; i++) {
117
+ const geos = [
118
+ new THREE.BoxGeometry(0.2, 0.2, 0.2),
119
+ new THREE.TetrahedronGeometry(0.15, 0),
120
+ new THREE.OctahedronGeometry(0.18, 0),
121
+ ];
122
+ const mats = [accentMat, blueSolid, accentSolid];
123
+ const x = (Math.random() - 0.5) * spread * 2;
124
+ const y = (Math.random() - 0.5) * spreadY * 2;
125
+ const z = Math.random() * 10;
126
+ addShape(geos[i % geos.length], mats[i % mats.length], x, y, z, 0.9 + Math.random() * 0.4);
127
+ }
128
 
129
+ // === CV-RELATED: Bounding box outlines (6) ===
130
+ for (let i = 0; i < 6; i++) {
131
+ const size = 1.5 + Math.random() * 3;
132
+ const corners = new Float32Array([
133
+ -size, -size, 0, size, -size, 0,
134
+ size, -size, 0, size, size, 0,
135
+ size, size, 0, -size, size, 0,
136
+ -size, size, 0, -size, -size, 0,
137
+ ]);
138
+ const geo = new THREE.BufferGeometry();
139
+ geo.setAttribute("position", new THREE.BufferAttribute(corners, 3));
140
+ const x = (Math.random() - 0.5) * spread * 1.8;
141
+ const y = (Math.random() - 0.5) * spreadY * 1.8;
142
+ const z = Math.random() * 6 - 3;
143
+ addShape(geo, i % 2 === 0 ? lineBlue : lineAccent, x, y, z, 0.4 + Math.random() * 0.3);
144
+ }
145
+
146
+ // === CV-RELATED: Crosshairs (4) ===
147
+ for (let i = 0; i < 4; i++) {
148
+ const s = 0.8 + Math.random() * 1.5;
149
+ const pts = new Float32Array([
150
+ -s, 0, 0, s, 0, 0,
151
+ 0, -s, 0, 0, s, 0,
152
+ ]);
153
+ const geo = new THREE.BufferGeometry();
154
+ geo.setAttribute("position", new THREE.BufferAttribute(pts, 3));
155
+ const x = (Math.random() - 0.5) * spread * 1.6;
156
+ const y = (Math.random() - 0.5) * spreadY * 1.6;
157
+ const z = Math.random() * 5 - 1;
158
+ addShape(geo, lineBlue, x, y, z, 0.6 + Math.random() * 0.3);
159
+ }
160
 
161
+ // === CV-RELATED: Diamond detection markers (4) ===
162
+ for (let i = 0; i < 4; i++) {
163
+ const s = 0.4 + Math.random() * 0.8;
164
+ const pts = new Float32Array([
165
+ 0, s, 0, s, 0, 0,
166
+ s, 0, 0, 0, -s, 0,
167
+ 0, -s, 0, -s, 0, 0,
168
+ -s, 0, 0, 0, s, 0,
169
+ ]);
170
+ const geo = new THREE.BufferGeometry();
171
+ geo.setAttribute("position", new THREE.BufferAttribute(pts, 3));
172
+ const x = (Math.random() - 0.5) * spread * 1.5;
173
+ const y = (Math.random() - 0.5) * spreadY * 1.5;
174
+ const z = Math.random() * 4;
175
+ addShape(geo, lineAccent, x, y, z, 0.5 + Math.random() * 0.3);
176
+ }
177
 
178
+ // === PARTICLES (300) ===
179
  const particleGeo = new THREE.BufferGeometry();
180
+ const particleCount = 300;
181
  const positions = new Float32Array(particleCount * 3);
182
+ const sizes = new Float32Array(particleCount);
183
  for (let i = 0; i < particleCount; i++) {
184
+ positions[i * 3] = (Math.random() - 0.5) * 90;
185
+ positions[i * 3 + 1] = (Math.random() - 0.5) * 60;
186
+ positions[i * 3 + 2] = (Math.random() - 0.5) * 35 - 5;
187
+ sizes[i] = 0.08 + Math.random() * 0.12;
188
  }
189
  particleGeo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
190
+ particleGeo.setAttribute("size", new THREE.BufferAttribute(sizes, 1));
191
+ const particleMat = new THREE.PointsMaterial({ color: 0x1e90ff, size: 0.14, transparent: true, opacity: 0.5 });
192
  const particles = new THREE.Points(particleGeo, particleMat);
193
  scene.add(particles);
194
 
195
+ // === TWINKLING STARS (120) ===
196
+ const starGeo = new THREE.BufferGeometry();
197
+ const starCount = 120;
198
+ const starPositions = new Float32Array(starCount * 3);
199
+ const starPhases = new Float32Array(starCount);
200
+ for (let i = 0; i < starCount; i++) {
201
+ starPositions[i * 3] = (Math.random() - 0.5) * 100;
202
+ starPositions[i * 3 + 1] = (Math.random() - 0.5) * 70;
203
+ starPositions[i * 3 + 2] = (Math.random() - 0.5) * 20 - 10;
204
+ starPhases[i] = Math.random() * Math.PI * 2;
205
+ }
206
+ starGeo.setAttribute("position", new THREE.BufferAttribute(starPositions, 3));
207
+ const starMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.08, transparent: true, opacity: 0.6 });
208
+ const stars = new THREE.Points(starGeo, starMat);
209
+ scene.add(stars);
210
+
211
+ // Mouse
212
  const onMouseMove = (e: MouseEvent) => {
213
  const rect = el.getBoundingClientRect();
214
  mouseRef.current.x = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
 
216
  };
217
  window.addEventListener("mousemove", onMouseMove);
218
 
219
+ // Click scatter
220
+ const onClick = (e: MouseEvent) => {
221
+ const rect = el.getBoundingClientRect();
222
+ const cx = ((e.clientX - rect.left) / rect.width - 0.5) * 2;
223
+ const cy = -((e.clientY - rect.top) / rect.height - 0.5) * 2;
224
+ shapes.forEach((s) => {
225
+ const dx = s.mesh.position.x - cx * 15;
226
+ const dy = s.mesh.position.y - cy * 10;
227
+ const dist = Math.sqrt(dx * dx + dy * dy) + 1;
228
+ const force = 4 / dist;
229
+ s.scatterX += (dx / dist) * force * s.speed;
230
+ s.scatterY += (dy / dist) * force * s.speed;
231
+ s.scatterZ += (Math.random() - 0.5) * force;
232
+ });
233
+ };
234
+ el.addEventListener("click", onClick);
235
+
236
  let raf: number;
237
  const animate = () => {
238
  raf = requestAnimationFrame(animate);
 
239
  const mx = mouseRef.current.x;
240
  const my = mouseRef.current.y;
241
+ const t = Date.now();
242
 
243
  shapes.forEach((s) => {
244
+ s.mesh.position.x += s.vx + Math.sin(t * 0.0003 * s.speed + s.baseX) * 0.006;
245
+ s.mesh.position.y += s.vy + Math.cos(t * 0.0004 * s.speed + s.baseY) * 0.005;
246
+ s.mesh.position.z += s.vz;
247
+ s.mesh.rotation.x += s.rvx;
248
+ s.mesh.rotation.y += s.rvy;
249
+
250
+ s.scatterX *= 0.95;
251
+ s.scatterY *= 0.95;
252
+ s.scatterZ *= 0.95;
253
+ s.mesh.position.x += s.scatterX;
254
+ s.mesh.position.y += s.scatterY;
255
+ s.mesh.position.z += s.scatterZ;
256
+
257
+ const dx = s.mesh.position.x - mx * 15;
258
+ const dy = s.mesh.position.y - (-my) * 10;
259
+ const dist = Math.sqrt(dx * dx + dy * dy) + 1;
260
+ if (dist < 8) {
261
+ const push = (8 - dist) * 0.015 * s.speed;
262
+ s.mesh.position.x += (dx / dist) * push;
263
+ s.mesh.position.y += (dy / dist) * push;
264
+ }
265
+
266
+ const bx = spread + 5, by = spreadY + 5, bz = 25;
267
+ if (s.mesh.position.x > bx) s.mesh.position.x = -bx;
268
+ if (s.mesh.position.x < -bx) s.mesh.position.x = bx;
269
+ if (s.mesh.position.y > by) s.mesh.position.y = -by;
270
+ if (s.mesh.position.y < -by) s.mesh.position.y = by;
271
+ if (s.mesh.position.z > 10) s.mesh.position.z = -bz;
272
+ if (s.mesh.position.z < -bz) s.mesh.position.z = 10;
273
  });
274
 
 
275
  camera.position.x += (mx * 2 - camera.position.x) * 0.03;
276
  camera.position.y += (-my * 1.5 - camera.position.y) * 0.03;
277
  camera.lookAt(0, 0, 0);
278
 
279
+ particles.rotation.y += 0.0004;
280
  particles.rotation.x += 0.0002;
281
 
282
+ // Twinkle stars
283
+ const starPos = stars.geometry.attributes.position as THREE.BufferAttribute;
284
+ for (let i = 0; i < starCount; i++) {
285
+ starMat.opacity = 0.3 + Math.sin(t * 0.002 + starPhases[i]) * 0.35;
286
+ }
287
+
288
  renderer.render(scene, camera);
289
  };
290
  animate();
 
301
  cancelAnimationFrame(raf);
302
  window.removeEventListener("mousemove", onMouseMove);
303
  window.removeEventListener("resize", onResize);
304
+ el.removeEventListener("click", onClick);
305
  el.removeChild(renderer.domElement);
306
  renderer.dispose();
307
  };
 
314
  position: "absolute",
315
  inset: 0,
316
  zIndex: 0,
 
317
  }}
318
  />
319
  );
frontend/components/StaggeredMenu.css DELETED
@@ -1 +0,0 @@
1
- .sm-wrap{position:fixed;inset:0;width:100dvw;height:100dvh;z-index:55;pointer-events:none;overflow:hidden}.sm-layers,.sm-panel{position:absolute;inset:0;left:auto;width:100%;height:100%;pointer-events:none;transform:translateX(100%)}.sm-layers{z-index:1}.sm-layer{position:absolute;inset:0;background:#fff}.sm-cream{background:#fff1e4}.sm-peach{background:#ffcf9e}.sm-orange{background:#ff7a00}.sm-panel{z-index:2;background:#fff;pointer-events:auto}.sm-inner{height:100%;padding:7.5rem 2rem 2rem;display:flex;flex-direction:column}.sm-kicker{font-size:.7rem;letter-spacing:.32em;font-weight:800;color:#ff7a00;margin:0 0 2.5rem}.sm-item-wrap{overflow:hidden;border-bottom:1px solid rgba(255,122,0,.2)}.sm-item{position:relative;display:inline-block;padding:1rem 3rem 1rem 0;color:#1d1712;text-decoration:none;font-family:Impact,Haettenschweiler,sans-serif;font-size:clamp(2.6rem,12vw,4.6rem);line-height:.95;letter-spacing:.03em}.sm-item:after{content:attr(data-number);position:absolute;right:.25rem;top:1rem;font:500 .7rem ui-monospace;color:#ff7a00}.sm-item:hover{color:#ff7a00}.sm-foot{margin-top:auto;color:#78695e;font-size:.65rem;letter-spacing:.13em;line-height:1.8}.sm-foot b{color:#ff7a00}.sm-toggle{position:fixed;z-index:60;top:15px;right:16px;width:38px;height:38px;border:0;background:transparent;pointer-events:auto;cursor:pointer;display:grid;place-content:center;gap:5px}.sm-toggle i{display:block;width:22px;height:2px;background:#1d1712;border-radius:2px}.sm-wrap[data-open] .sm-toggle i:nth-child(1){transform:translateY(7px) rotate(45deg)}.sm-wrap[data-open] .sm-toggle i:nth-child(2){opacity:0}.sm-wrap[data-open] .sm-toggle i:nth-child(3){transform:translateY(-7px) rotate(-45deg)}@media(min-width:768px){.sm-panel,.sm-layers{width:440px}.sm-toggle{right:calc((100vw - min(1200px,92vw))/2)} }
 
 
frontend/components/StaggeredMenu.tsx DELETED
@@ -1,68 +0,0 @@
1
- "use client";
2
- import Link from "next/link";
3
- import { gsap } from "gsap";
4
- import { useLayoutEffect, useRef, useState, useEffect } from "react";
5
- import "./StaggeredMenu.css";
6
- type Item = { label: string; ariaLabel: string; link: string };
7
- export default function StaggeredMenu({ items }: { items: Item[] }) {
8
- const [open, setOpen] = useState(false);
9
- const [mounted, setMounted] = useState(false);
10
- const panel = useRef<HTMLDivElement>(null);
11
- const layers = useRef<HTMLDivElement>(null);
12
- const button = useRef<HTMLButtonElement>(null);
13
- const labels = useRef<HTMLSpanElement[]>([]);
14
-
15
- useEffect(() => {
16
- setMounted(true);
17
- }, []);
18
-
19
- useLayoutEffect(() => {
20
- if (!mounted) return;
21
- const all = [panel.current, ...Array.from(layers.current?.children || [])].filter(Boolean);
22
- gsap.set(all, { xPercent: 100 });
23
- gsap.set(labels.current, { yPercent: 140, rotate: 10 });
24
- }, [mounted]);
25
-
26
- function toggle() {
27
- const next = !open;
28
- setOpen(next);
29
- const layerEls = Array.from(layers.current?.children || []);
30
- if (next) {
31
- const tl = gsap.timeline();
32
- tl.to(layerEls, { xPercent: 0, duration: .5, ease: "power4.out", stagger: .07 })
33
- .to(panel.current, { xPercent: 0, duration: .65, ease: "power4.out" }, .15)
34
- .to(labels.current, { yPercent: 0, rotate: 0, duration: 1, ease: "power4.out", stagger: .1 }, .35)
35
- .to(button.current, { rotate: 180, duration: .5, ease: "power4.out" }, 0);
36
- } else {
37
- gsap.to([panel.current, ...layerEls], { xPercent: 100, duration: .32, ease: "power3.in" });
38
- gsap.to(labels.current, { yPercent: 140, rotate: 10, duration: .15 });
39
- gsap.to(button.current, { rotate: 0, duration: .35 });
40
- }
41
- }
42
-
43
- return (
44
- <div className="sm-wrap" data-open={open || undefined} style={{ display: mounted ? undefined : 'none' }}>
45
- <div ref={layers} className="sm-layers" aria-hidden="true">
46
- <div className="sm-layer sm-cream" />
47
- <div className="sm-layer sm-peach" />
48
- <div className="sm-layer sm-orange" />
49
- </div>
50
- <aside ref={panel} className="sm-panel" aria-hidden={!open}>
51
- <div className="sm-inner">
52
- <p className="sm-kicker">FaceAI // NAVIGATION</p>
53
- <nav>{items.map((item, i) => (
54
- <div className="sm-item-wrap" key={item.link}>
55
- <Link href={item.link} aria-label={item.ariaLabel} onClick={toggle} className="sm-item" data-number={`0${i + 1}`}>
56
- <span ref={el => { if (el) labels.current[i] = el }}>{item.label}</span>
57
- </Link>
58
- </div>
59
- ))}</nav>
60
- <p className="sm-foot">CONSENTED BIOMETRIC RECOGNITION<br/><b>FaceAI //</b></p>
61
- </div>
62
- </aside>
63
- <button ref={button} className="sm-toggle" aria-label={open ? 'Close menu' : 'Open menu'} aria-expanded={open} onClick={toggle}>
64
- <i /><i /><i />
65
- </button>
66
- </div>
67
- );
68
- }