@naeil/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +60 -21
  2. package/dist/chunk-JBTDRVZ7.js +46 -0
  3. package/dist/chunk-SYI3IKIC.js +355 -0
  4. package/dist/components/index.d.ts +87 -0
  5. package/dist/components/index.js +832 -0
  6. package/dist/components/ui/index.d.ts +83 -0
  7. package/dist/components/ui/index.js +481 -0
  8. package/dist/lib/utils.d.ts +5 -0
  9. package/dist/lib/utils.js +7 -0
  10. package/package.json +52 -11
  11. package/src/app/globals.css +1 -1
  12. package/src/styles/theme.css +206 -0
  13. package/postcss.config.mjs +0 -7
  14. package/src/app/[locale]/(content)/baby-agent/page.tsx +0 -30
  15. package/src/app/[locale]/(content)/blog/page.tsx +0 -68
  16. package/src/app/[locale]/(content)/cc/page.tsx +0 -216
  17. package/src/app/[locale]/(content)/design/page.tsx +0 -703
  18. package/src/app/[locale]/(content)/layout.tsx +0 -7
  19. package/src/app/[locale]/(content)/pkm/page.tsx +0 -31
  20. package/src/app/[locale]/(content)/projects/page.tsx +0 -100
  21. package/src/app/[locale]/(content)/sa/page.tsx +0 -179
  22. package/src/app/[locale]/(landing)/page.tsx +0 -157
  23. package/src/app/[locale]/layout.tsx +0 -72
  24. package/src/app/[locale]/login/actions.ts +0 -10
  25. package/src/app/[locale]/login/oauth-buttons.tsx +0 -78
  26. package/src/app/[locale]/login/page.tsx +0 -85
  27. package/src/app/auth/callback/route.ts +0 -78
  28. package/src/app/favicon.ico +0 -0
  29. package/src/app/layout.tsx +0 -22
  30. package/src/app/sitemap.ts +0 -26
  31. package/src/components/accent-picker.tsx +0 -62
  32. package/src/components/auth-slot.tsx +0 -96
  33. package/src/components/footer-wrapper.tsx +0 -8
  34. package/src/components/footer.tsx +0 -193
  35. package/src/components/hero-scene.tsx +0 -948
  36. package/src/components/hero-section.tsx +0 -57
  37. package/src/components/locale-switcher.tsx +0 -74
  38. package/src/components/logo.tsx +0 -51
  39. package/src/components/nav-server-wrapper.tsx +0 -22
  40. package/src/components/nav-wrapper.tsx +0 -45
  41. package/src/components/nav.tsx +0 -550
  42. package/src/components/paraglider-cursor.tsx +0 -112
  43. package/src/components/project-layout.tsx +0 -194
  44. package/src/components/theme-provider.tsx +0 -10
  45. package/src/components/theme-toggle-icon.tsx +0 -62
  46. package/src/components/theme-toggle.tsx +0 -39
  47. package/src/components/typography.tsx +0 -27
  48. package/src/components/ui/avatar.tsx +0 -109
  49. package/src/components/ui/badge.tsx +0 -47
  50. package/src/components/ui/button.tsx +0 -65
  51. package/src/components/ui/card.tsx +0 -106
  52. package/src/components/ui/dialog.tsx +0 -158
  53. package/src/components/ui/dropdown-menu.tsx +0 -257
  54. package/src/components/ui/input.tsx +0 -21
  55. package/src/components/ui/sonner.tsx +0 -54
  56. package/src/components/workflow-diagram.tsx +0 -414
  57. package/src/i18n/config.ts +0 -8
  58. package/src/i18n/request.ts +0 -16
  59. package/src/i18n/routing.ts +0 -12
  60. package/src/lib/supabase/client.ts +0 -18
  61. package/src/lib/supabase/middleware.ts +0 -42
  62. package/src/lib/supabase/server.ts +0 -37
  63. package/src/lib/utils.ts +0 -6
  64. package/src/proxy.ts +0 -62
  65. package/src/tokens/.gitkeep +0 -0
  66. package/src/tokens/color.json +0 -56
  67. package/src/tokens/radius.json +0 -9
  68. package/src/tokens/semantic.json +0 -81
  69. package/src/tokens/shadow.json +0 -7
  70. package/src/tokens/spacing.json +0 -16
  71. package/src/tokens/typography.json +0 -34
  72. package/src/types/i18n.d.ts +0 -6
  73. package/src/utils/.gitkeep +0 -0
@@ -1,550 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState } from "react";
4
- import { useTranslations } from "next-intl";
5
- import { Badge } from "./ui/badge";
6
- import Image from "next/image";
7
-
8
- const PROJECTS = [
9
- {
10
- key: "cc",
11
- href: "/cc",
12
- accent: "oklch(0.704 0.140 181)",
13
- icon: "/images/coral.png",
14
- status: "active" as const,
15
- },
16
- {
17
- key: "pkm",
18
- href: "/pkm",
19
- accent: "oklch(0.702 0.183 293)",
20
- icon: "/images/jellyfish.png",
21
- status: "coming" as const,
22
- },
23
- {
24
- key: "naeilUi",
25
- href: "/design",
26
- accent: "oklch(0.623 0.214 259)",
27
- icon: "/images/whale.png",
28
- status: "active" as const,
29
- },
30
- {
31
- key: "sa",
32
- href: "/sa",
33
- accent: "oklch(0.723 0.219 149)",
34
- icon: "/images/fish.png",
35
- status: "active" as const,
36
- },
37
- {
38
- key: "babyAgent",
39
- href: "/baby-agent",
40
- accent: "oklch(0.769 0.188 70.08)",
41
- icon: "/images/turtle.png",
42
- status: "coming" as const,
43
- },
44
- ];
45
-
46
- interface NavProps {
47
- /** Internal Link component (from consumer's next-intl routing) */
48
- Link: React.ComponentType<{
49
- href: string;
50
- children: React.ReactNode;
51
- className?: string;
52
- onClick?: () => void;
53
- [key: string]: any;
54
- }>;
55
- /** Current pathname value (already resolved, not a hook) */
56
- usePathname: () => string;
57
- /** Where the .naeil logo links to. Default: "/" */
58
- brandHref?: string;
59
- /** Whether brandHref is external (renders <a>) or internal (renders Link). Default: false */
60
- brandExternal?: boolean;
61
- /** Custom brand content (replaces default Logo + ".naeil" text) */
62
- brandSlot?: React.ReactNode;
63
- /** When true, project links (except current) are rendered as external <a> to externalBaseUrl. Default: false */
64
- projectsExternal?: boolean;
65
- /** Base URL for external project links. Default: "https://naeil.dev" */
66
- externalBaseUrl?: string;
67
- /** Override href for the current project in the dropdown */
68
- currentProjectKey?: string;
69
- currentProjectHref?: string;
70
- /** App-specific sub-navigation items after the project dropdown */
71
- subNav?: Array<{ label: string; href: string }>;
72
- /** React node for auth UI (rendered in desktop right side + mobile bottom) */
73
- authSlot?: React.ReactNode;
74
- /** Show Projects dropdown in nav. Default: true */
75
- showProjects?: boolean;
76
- /** Show blog link in nav. Default: true */
77
- showBlog?: boolean;
78
- /** Right-side toolbar (theme toggle, locale switcher, etc.) */
79
- toolbarSlot?: React.ReactNode;
80
- }
81
-
82
- export function Nav({
83
- Link,
84
- usePathname,
85
- brandHref = "/",
86
- brandExternal = false,
87
- brandSlot,
88
- projectsExternal = false,
89
- externalBaseUrl = "https://naeil.dev",
90
- currentProjectKey,
91
- currentProjectHref,
92
- subNav,
93
- authSlot,
94
- showProjects = true,
95
- showBlog = true,
96
- toolbarSlot,
97
- }: NavProps) {
98
- const t = useTranslations("nav");
99
- const pt = useTranslations("projects");
100
- const pathname = usePathname();
101
- const [scrolled, setScrolled] = useState(false);
102
- const [mobileOpen, setMobileOpen] = useState(false);
103
- const [projectsOpen, setProjectsOpen] = useState(false);
104
-
105
- useEffect(() => {
106
- const onScroll = () => setScrolled(window.scrollY > 16);
107
- window.addEventListener("scroll", onScroll, { passive: true });
108
- return () => window.removeEventListener("scroll", onScroll);
109
- }, []);
110
-
111
- // Close mobile menu on route change
112
- useEffect(() => {
113
- setMobileOpen(false);
114
- setProjectsOpen(false);
115
- }, [pathname]);
116
-
117
- // Prevent body scroll when mobile menu open
118
- useEffect(() => {
119
- document.body.style.overflow = mobileOpen ? "hidden" : "";
120
- return () => {
121
- document.body.style.overflow = "";
122
- };
123
- }, [mobileOpen]);
124
-
125
- const linkClass = (href: string) => {
126
- const isActive = pathname === href;
127
- return [
128
- "text-sm font-medium transition-colors",
129
- isActive
130
- ? "text-foreground"
131
- : "text-muted-foreground hover:text-foreground",
132
- ].join(" ");
133
- };
134
-
135
- const isProjectActive =
136
- pathname.startsWith("/projects") ||
137
- pathname.startsWith("/cc") ||
138
- pathname.startsWith("/pkm") ||
139
- pathname.startsWith("/sa") ||
140
- pathname.startsWith("/design");
141
-
142
- // Resolve project href and whether it's external
143
- function getProjectLink(project: (typeof PROJECTS)[number]) {
144
- if (currentProjectKey && project.key === currentProjectKey) {
145
- return { href: currentProjectHref || "/", external: false };
146
- }
147
- if (projectsExternal) {
148
- return { href: `${externalBaseUrl}${project.href}`, external: true };
149
- }
150
- return { href: project.href, external: false };
151
- }
152
-
153
- // Brand logo element
154
- const brandContent = brandSlot ?? (
155
- <span className="text-foreground flex items-center gap-3 text-xl font-semibold tracking-tight">
156
- <Logo size={28} />
157
- .naeil
158
- </span>
159
- );
160
-
161
- const brandElement = brandExternal ? (
162
- <a href={brandHref}>{brandContent}</a>
163
- ) : (
164
- <Link href={brandHref}>{brandContent}</Link>
165
- );
166
-
167
- return (
168
- <>
169
- <nav
170
- className={[
171
- "fixed top-0 z-50 w-full transition-[background-color,border-color] duration-300",
172
- scrolled || mobileOpen
173
- ? "border-b border-border/40 bg-background/80 backdrop-blur-md"
174
- : "border-b border-transparent bg-transparent",
175
- ].join(" ")}
176
- >
177
- <div className="mx-auto flex h-[58px] max-w-7xl items-center px-6">
178
- {/* Logo */}
179
- <div className="flex shrink-0 items-center md:w-[200px]">
180
- {brandElement}
181
- </div>
182
-
183
- {/* Desktop: Nav links */}
184
- <div className="hidden flex-1 items-center gap-6 md:flex">
185
- {/* Projects dropdown */}
186
- {showProjects && <div className="group relative">
187
- <button
188
- className={[
189
- "text-sm font-medium transition-colors",
190
- isProjectActive
191
- ? "text-foreground"
192
- : "text-muted-foreground hover:text-foreground",
193
- ].join(" ")}
194
- >
195
- {t("projects")}
196
- <svg
197
- width="10"
198
- height="10"
199
- viewBox="0 0 24 24"
200
- fill="none"
201
- stroke="currentColor"
202
- strokeWidth="2"
203
- strokeLinecap="round"
204
- strokeLinejoin="round"
205
- className="ml-1 inline-block transition-transform group-hover:rotate-180"
206
- >
207
- <path d="M6 9l6 6 6-6" />
208
- </svg>
209
- </button>
210
-
211
- {/* Hover mega-dropdown */}
212
- <div className="pointer-events-none absolute left-0 top-full pt-3 opacity-0 transition-opacity duration-150 group-hover:pointer-events-auto group-hover:opacity-100">
213
- <div className="border-border/40 bg-background/95 w-[520px] rounded-xl border p-4 shadow-xl backdrop-blur-md">
214
- <div className="grid grid-cols-2 gap-1">
215
- {PROJECTS.map((project) => {
216
- const { href, external } = getProjectLink(project);
217
- const inner = (
218
- <div className="hover:bg-muted/50 flex items-start gap-3.5 rounded-lg px-3 py-3 transition-colors">
219
- <div
220
- className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg border"
221
- style={{
222
- borderColor: `color-mix(in oklch, ${project.accent} 30%, transparent)`,
223
- backgroundColor: `color-mix(in oklch, ${project.accent} 8%, transparent)`,
224
- }}
225
- >
226
- <Image
227
- src={project.icon}
228
- alt=""
229
- width={24}
230
- height={24}
231
- className="object-contain"
232
- />
233
- </div>
234
- <div className="min-w-0 flex-1 pt-0.5">
235
- <div className="flex items-center gap-2">
236
- <span className="text-foreground text-sm font-medium">
237
- {pt(`${project.key}.name`)}
238
- </span>
239
- {project.status === "coming" && (
240
- <Badge
241
- variant="outline"
242
- className="text-muted-foreground px-1 py-0 text-[9px]"
243
- >
244
- {pt("status.coming")}
245
- </Badge>
246
- )}
247
- </div>
248
- <p className="text-muted-foreground mt-0.5 text-xs leading-snug">
249
- {pt(`${project.key}.description`)}
250
- </p>
251
- </div>
252
- </div>
253
- );
254
-
255
- if (external) {
256
- return (
257
- <a key={project.key} href={href}>
258
- {inner}
259
- </a>
260
- );
261
- }
262
- return (
263
- <Link key={project.key} href={href}>
264
- {inner}
265
- </Link>
266
- );
267
- })}
268
- </div>
269
- </div>
270
- </div>
271
- </div>}
272
-
273
- {/* Blog link (conditional) */}
274
- {showBlog && (
275
- <>
276
- {projectsExternal ? (
277
- <a
278
- href={`${externalBaseUrl}/blog`}
279
- className="text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
280
- >
281
- {t("blog")}
282
- </a>
283
- ) : (
284
- <Link href="/blog" className={linkClass("/blog")}>
285
- {t("blog")}
286
- </Link>
287
- )}
288
- </>
289
- )}
290
-
291
- {/* Divider before sub-nav */}
292
- {subNav && subNav.length > 0 && (
293
- <>
294
- <div className="h-4 w-px bg-border/40" />
295
- {subNav.map((item) => (
296
- <Link
297
- key={item.href}
298
- href={item.href}
299
- className={linkClass(item.href)}
300
- >
301
- {item.label}
302
- </Link>
303
- ))}
304
- </>
305
- )}
306
- </div>
307
-
308
- {/* Desktop: Auth + Toolbar */}
309
- <div className="hidden items-center gap-3 md:flex">
310
- {authSlot}
311
- {authSlot && toolbarSlot && (
312
- <div className="h-4 w-px bg-border/40" />
313
- )}
314
- {toolbarSlot && (
315
- <div className="flex items-center gap-4">{toolbarSlot}</div>
316
- )}
317
- </div>
318
-
319
- {/* Mobile: Hamburger */}
320
- <button
321
- className="text-muted-foreground hover:text-foreground ml-auto transition-colors md:hidden"
322
- onClick={() => setMobileOpen(!mobileOpen)}
323
- aria-label="Menu"
324
- >
325
- {mobileOpen ? <CloseIcon /> : <MenuIcon />}
326
- </button>
327
- </div>
328
- </nav>
329
-
330
- {/* Mobile menu overlay */}
331
- {mobileOpen && (
332
- <div className="bg-background/95 fixed inset-0 z-40 overflow-y-auto pt-[58px] backdrop-blur-md md:hidden">
333
- <div className="flex flex-col px-6 py-6">
334
- {/* Projects accordion */}
335
- {showProjects && (
336
- <>
337
- <button
338
- className="text-foreground flex items-center justify-between py-3 text-base font-medium"
339
- onClick={() => setProjectsOpen(!projectsOpen)}
340
- >
341
- {t("projects")}
342
- <svg
343
- width="14"
344
- height="14"
345
- viewBox="0 0 24 24"
346
- fill="none"
347
- stroke="currentColor"
348
- strokeWidth="2"
349
- strokeLinecap="round"
350
- strokeLinejoin="round"
351
- className={[
352
- "transition-transform duration-200",
353
- projectsOpen ? "rotate-180" : "",
354
- ].join(" ")}
355
- >
356
- <path d="M6 9l6 6 6-6" />
357
- </svg>
358
- </button>
359
-
360
- {projectsOpen && (
361
- <div className="mb-2 flex flex-col gap-1 pl-1">
362
- {PROJECTS.map((project) => {
363
- const { href, external } = getProjectLink(project);
364
- const inner = (
365
- <div className="hover:bg-muted/50 flex items-center gap-3 rounded-lg px-3 py-3 transition-colors">
366
- <div
367
- className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border"
368
- style={{
369
- borderColor: `color-mix(in oklch, ${project.accent} 30%, transparent)`,
370
- backgroundColor: `color-mix(in oklch, ${project.accent} 8%, transparent)`,
371
- }}
372
- >
373
- <Image
374
- src={project.icon}
375
- alt=""
376
- width={20}
377
- height={20}
378
- className="object-contain"
379
- />
380
- </div>
381
- <div>
382
- <span className="text-foreground text-sm font-medium">
383
- {pt(`${project.key}.name`)}
384
- </span>
385
- {project.status === "coming" && (
386
- <Badge
387
- variant="outline"
388
- className="text-muted-foreground ml-2 px-1 py-0 text-[9px]"
389
- >
390
- {pt("status.coming")}
391
- </Badge>
392
- )}
393
- </div>
394
- </div>
395
- );
396
-
397
- if (external) {
398
- return (
399
- <a
400
- key={project.key}
401
- href={href}
402
- onClick={() => setMobileOpen(false)}
403
- >
404
- {inner}
405
- </a>
406
- );
407
- }
408
- return (
409
- <Link
410
- key={project.key}
411
- href={href}
412
- onClick={() => setMobileOpen(false)}
413
- >
414
- {inner}
415
- </Link>
416
- );
417
- })}
418
- </div>
419
- )}
420
- </>
421
- )}
422
-
423
- {/* Blog link (conditional) */}
424
- {showBlog && (
425
- <>
426
- {projectsExternal ? (
427
- <a
428
- href={`${externalBaseUrl}/blog`}
429
- className="text-foreground border-border/40 border-t py-3 text-base font-medium"
430
- onClick={() => setMobileOpen(false)}
431
- >
432
- {t("blog")}
433
- </a>
434
- ) : (
435
- <Link
436
- href="/blog"
437
- className="text-foreground border-border/40 border-t py-3 text-base font-medium"
438
- onClick={() => setMobileOpen(false)}
439
- >
440
- {t("blog")}
441
- </Link>
442
- )}
443
- </>
444
- )}
445
-
446
- {/* Sub-nav items (mobile) */}
447
- {subNav && subNav.length > 0 && (
448
- <div className="border-border/40 border-t">
449
- {subNav.map((item) => (
450
- <Link
451
- key={item.href}
452
- href={item.href}
453
- className="text-foreground block py-3 text-base font-medium"
454
- onClick={() => setMobileOpen(false)}
455
- >
456
- {item.label}
457
- </Link>
458
- ))}
459
- </div>
460
- )}
461
-
462
- {/* Auth section mobile */}
463
- {authSlot && (
464
- <div className="border-border/40 border-t pt-4">
465
- {authSlot}
466
- </div>
467
- )}
468
-
469
- {/* Toolbar (Theme + Locale) */}
470
- {toolbarSlot && (
471
- <div className="border-border/40 flex items-center gap-4 border-t pt-4 mt-2">
472
- {toolbarSlot}
473
- </div>
474
- )}
475
- </div>
476
- </div>
477
- )}
478
- </>
479
- );
480
- }
481
-
482
- /* ── Inline Logo (pure SVG, no external deps) ── */
483
- function Logo({
484
- size = 20,
485
- }: {
486
- size?: number;
487
- }) {
488
- const id = `naeil-nav-logo-${size}`;
489
- return (
490
- <svg
491
- width={size}
492
- height={size}
493
- viewBox="0 0 24 24"
494
- fill="none"
495
- aria-label="naeil"
496
- >
497
- <path
498
- d="M6 4 L6 16 L20 16"
499
- stroke="currentColor"
500
- strokeWidth="2"
501
- strokeLinecap="square"
502
- strokeLinejoin="miter"
503
- />
504
- <circle cx="15" cy="16" r="3.5" fill={`url(#${id}-grad)`} mask={`url(#${id})`} />
505
- <defs>
506
- <linearGradient id={`${id}-grad`} x1="15" y1="12.5" x2="15" y2="16">
507
- <stop stopColor="#eab308" />
508
- <stop offset="1" stopColor="#dc2626" />
509
- </linearGradient>
510
- <mask id={id}>
511
- <rect x="0" y="0" width="24" height="16" fill="white" />
512
- </mask>
513
- </defs>
514
- </svg>
515
- );
516
- }
517
-
518
- function MenuIcon() {
519
- return (
520
- <svg
521
- width="22"
522
- height="22"
523
- viewBox="0 0 24 24"
524
- fill="none"
525
- stroke="currentColor"
526
- strokeWidth="1.5"
527
- strokeLinecap="round"
528
- strokeLinejoin="round"
529
- >
530
- <path d="M4 6h16M4 12h16M4 18h16" />
531
- </svg>
532
- );
533
- }
534
-
535
- function CloseIcon() {
536
- return (
537
- <svg
538
- width="22"
539
- height="22"
540
- viewBox="0 0 24 24"
541
- fill="none"
542
- stroke="currentColor"
543
- strokeWidth="1.5"
544
- strokeLinecap="round"
545
- strokeLinejoin="round"
546
- >
547
- <path d="M18 6L6 18M6 6l12 12" />
548
- </svg>
549
- );
550
- }
@@ -1,112 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useRef, useState } from "react";
4
-
5
- export function ParagliderCursor({ containerRef }: { containerRef: React.RefObject<HTMLElement | null> }) {
6
- const gliderRef = useRef<HTMLDivElement>(null);
7
- const pos = useRef({ x: 0, y: 0 });
8
- const target = useRef({ x: 0, y: 0 });
9
- const visible = useRef(false);
10
- const direction = useRef(1); // 1 = right, -1 = left
11
- const [show, setShow] = useState(false);
12
-
13
- useEffect(() => {
14
- const container = containerRef.current;
15
- if (!container) return;
16
-
17
- const onMove = (e: MouseEvent) => {
18
- const rect = container.getBoundingClientRect();
19
- target.current = {
20
- x: e.clientX - rect.left,
21
- y: e.clientY - rect.top,
22
- };
23
- // Track direction for tilt
24
- const dx = target.current.x - pos.current.x;
25
- if (Math.abs(dx) > 1) {
26
- direction.current = dx > 0 ? 1 : -1;
27
- }
28
- if (!visible.current) {
29
- visible.current = true;
30
- setShow(true);
31
- // Snap to initial position (no lag on first enter)
32
- pos.current = { ...target.current };
33
- }
34
- };
35
-
36
- const onLeave = () => {
37
- visible.current = false;
38
- setShow(false);
39
- };
40
-
41
- container.addEventListener("mousemove", onMove);
42
- container.addEventListener("mouseleave", onLeave);
43
-
44
- let raf: number;
45
- const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
46
-
47
- const animate = () => {
48
- pos.current.x = lerp(pos.current.x, target.current.x, 0.06);
49
- pos.current.y = lerp(pos.current.y, target.current.y, 0.06);
50
-
51
- if (gliderRef.current) {
52
- const dx = target.current.x - pos.current.x;
53
- const tilt = Math.max(-15, Math.min(15, dx * 0.3));
54
- gliderRef.current.style.transform = `translate(${pos.current.x - 24}px, ${pos.current.y - 32}px) rotate(${tilt}deg)`;
55
- }
56
- raf = requestAnimationFrame(animate);
57
- };
58
- raf = requestAnimationFrame(animate);
59
-
60
- return () => {
61
- container.removeEventListener("mousemove", onMove);
62
- container.removeEventListener("mouseleave", onLeave);
63
- cancelAnimationFrame(raf);
64
- };
65
- }, [containerRef]);
66
-
67
- return (
68
- <div
69
- ref={gliderRef}
70
- className="pointer-events-none absolute left-0 top-0 z-20 transition-opacity duration-300"
71
- style={{ opacity: show ? 1 : 0 }}
72
- >
73
- <svg
74
- width="48"
75
- height="48"
76
- viewBox="0 0 64 64"
77
- fill="none"
78
- xmlns="http://www.w3.org/2000/svg"
79
- >
80
- {/* Canopy */}
81
- <path
82
- d="M8 22 Q12 6 32 4 Q52 6 56 22 Q44 18 32 20 Q20 18 8 22Z"
83
- fill="white"
84
- fillOpacity="0.7"
85
- />
86
- {/* Lines */}
87
- <line x1="12" y1="21" x2="30" y2="42" stroke="white" strokeWidth="0.6" opacity="0.5" />
88
- <line x1="22" y1="19" x2="31" y2="42" stroke="white" strokeWidth="0.6" opacity="0.5" />
89
- <line x1="32" y1="18" x2="32" y2="42" stroke="white" strokeWidth="0.6" opacity="0.5" />
90
- <line x1="42" y1="19" x2="33" y2="42" stroke="white" strokeWidth="0.6" opacity="0.5" />
91
- <line x1="52" y1="21" x2="34" y2="42" stroke="white" strokeWidth="0.6" opacity="0.5" />
92
- {/* Person */}
93
- <circle cx="32" cy="45" r="2.5" fill="white" fillOpacity="0.8" />
94
- <path
95
- d="M30 48 L32 56 L34 48"
96
- stroke="white"
97
- strokeWidth="1.2"
98
- strokeLinecap="round"
99
- strokeLinejoin="round"
100
- opacity="0.7"
101
- />
102
- <path
103
- d="M28 51 L32 49 L36 51"
104
- stroke="white"
105
- strokeWidth="1"
106
- strokeLinecap="round"
107
- opacity="0.6"
108
- />
109
- </svg>
110
- </div>
111
- );
112
- }