diff --git a/app/about/page.tsx b/app/about/page.tsx index 3c24982..a7e6658 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -1,6 +1,8 @@ import { GitPullRequestArrowIcon } from "lucide-react"; import type { Metadata } from "next"; import Link from "next/link"; +import { logout } from "@/actions/auth"; +import { DockNav } from "@/components/dock-nav"; import { Footer } from "@/components/footer"; import { Navbar } from "@/components/navbar"; import { Alert, AlertDescription } from "@/components/ui/alert"; @@ -11,9 +13,10 @@ export const metadata: Metadata = { export default function Home() { return ( -
+
+
diff --git a/app/forum/components/logout-button.tsx b/app/forum/components/logout-button.tsx index 6bf2ac6..d29435e 100644 --- a/app/forum/components/logout-button.tsx +++ b/app/forum/components/logout-button.tsx @@ -13,9 +13,9 @@ export function LogoutButton() { className="grid place-items-center" > {pending ? ( - + ) : ( - + )} ); diff --git a/app/forum/components/post-card.tsx b/app/forum/components/post-card.tsx index 74ae17a..0c85cb6 100644 --- a/app/forum/components/post-card.tsx +++ b/app/forum/components/post-card.tsx @@ -1,8 +1,14 @@ -import { CalendarIcon } from "lucide-react"; +import { CalendarIcon, Clock3Icon, MessageCircleIcon } from "lucide-react"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { Badge } from "@/components/ui/badge"; -import { Card, CardContent, CardFooter, CardTitle } from "@/components/ui/card"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import type { Post, Tag } from "@/db/schema"; import { formatDate, truncateContent } from "@/lib/utils"; @@ -11,36 +17,56 @@ type Props = { }; export function PostCard({ post }: Props) { + const sanitizedContent = truncateContent(post.content).replace(/\s+/g, " "); + const wordCount = post.content.trim().split(/\s+/).length; + const readingTime = Math.max(1, Math.round(wordCount / 180)); + const showGradient = sanitizedContent.length > 180; + return ( - - - + + +
+ + Anonymous post +
+ {post.title} -
- - {truncateContent(post.content).replace(/\s+/g, " ")} - + + + +
+
+ {sanitizedContent} +
+ {showGradient && ( +
+ )}
-
+
{post.tags.map((tag) => ( - + {tag.name} ))}
-
-
- -
- - Posted {formatDate(post.createdAt)} -
-
-
+ +
+ + {formatDate(post.createdAt)} +
+
+ + {readingTime} min read +
+
); } diff --git a/app/forum/layout.tsx b/app/forum/layout.tsx index 92d7320..90e41bd 100644 --- a/app/forum/layout.tsx +++ b/app/forum/layout.tsx @@ -1,18 +1,9 @@ -import { - HomeIcon, - InfoIcon, - MessageCirclePlusIcon, - SquareCodeIcon, -} from "lucide-react"; import type { Metadata } from "next"; -import Link from "next/link"; import { redirect } from "next/navigation"; - import { logout } from "@/actions/auth"; -import { Button } from "@/components/ui/button"; +import { DockNav } from "@/components/dock-nav"; import { getSession } from "@/lib/auth"; import { ForumNavbar } from "./components/forum-navbar"; -import { LogoutButton } from "./components/logout-button"; export const metadata: Metadata = { title: "Umedu — Private Forum", @@ -37,26 +28,7 @@ export default async function ForumLayout({ {children} - -
- - - - - - - - - - -
- - -
+
); } diff --git a/app/forum/page.tsx b/app/forum/page.tsx index 5f2f91d..648eced 100644 --- a/app/forum/page.tsx +++ b/app/forum/page.tsx @@ -3,10 +3,23 @@ import { useThrottledCallback } from "@tanstack/react-pacer/throttler"; import { useInfiniteQuery } from "@tanstack/react-query"; import { useWindowVirtualizer } from "@tanstack/react-virtual"; -import { AlertCircleIcon, MessageCircleDashedIcon } from "lucide-react"; +import { + AlertCircleIcon, + MessageCircleDashedIcon, + MessageCirclePlusIcon, + ShieldCheckIcon, +} from "lucide-react"; +import Link from "next/link"; import { useEffect } from "react"; +import DecryptedText from "@/components/DecryptedText"; import { HoverPrefetchLink } from "@/components/hover-prefetch-link"; +import ShinyText from "@/components/ShinyText"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { ProgressiveBlur } from "@/components/ui/progressive-blur"; +import { Separator } from "@/components/ui/separator"; import type { Post, Tag } from "@/db/schema"; import { PostCard } from "./components/post-card"; import { PostCardSkeleton } from "./components/post-card-skeleton"; @@ -108,63 +121,124 @@ export default function FeedPage() { return (
+ +
+
+
+ + + +
+

+ usls.edu.ph +

+

+ Your campus peers are reading in real time. Share honest wins, + worries, or random shower thoughts. +

+
+
+
+ + + + + Share it anonymously in seconds. + + + + + +

+ + +

+ +
+
+
+ {allPosts.length === 0 && !isFetching && ( - No posts yet - - Start the conversation by creating a new post! + No posts just yet + +

+ Your note could be the first spark - drop a question or story. +

+
)} -
- {items.map((virtualRow) => { - const isLoaderRow = virtualRow.index > allPosts.length - 1; - const post = allPosts[virtualRow.index]; - - if (!isLoaderRow && !post) return null; - - return ( -
- {isLoaderRow ? ( - hasNextPage ? ( - + + +
+

+ Latest discussions +

+
+ {items.map((virtualRow) => { + const isLoaderRow = virtualRow.index > allPosts.length - 1; + const post = allPosts[virtualRow.index]; + + if (!isLoaderRow && !post) return null; + + return ( +
+ {isLoaderRow ? ( + hasNextPage ? ( + + ) : ( +
+ Nothing more to load +
+ ) ) : ( -
- Nothing more to load -
- ) - ) : ( - - - - )} -
- ); - })} -
+ + + + )} +
+ ); + })} +
+
); } diff --git a/app/forum/submit/page.tsx b/app/forum/submit/page.tsx index 5aa4f5f..4c7e9c3 100644 --- a/app/forum/submit/page.tsx +++ b/app/forum/submit/page.tsx @@ -1,11 +1,27 @@ "use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { SendHorizonalIcon, XIcon } from "lucide-react"; +import { + ArrowLeftIcon, + LightbulbIcon, + SendHorizonalIcon, + ShieldCheckIcon, + SparklesIcon, + XIcon, +} from "lucide-react"; +import Link from "next/link"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { z } from "zod/v4"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { useAppForm } from "@/hooks/form"; import { getTagsQuery } from "@/lib/queries"; @@ -28,6 +44,36 @@ export default function SubmitPage() { const queryClient = useQueryClient(); const { data: tags } = useQuery(getTagsQuery); + const postingTips = [ + { + title: "Lead with context", + description: + "Share just enough detail so peers can quickly understand the situation.", + icon: LightbulbIcon, + }, + { + title: "Ask one clear question", + description: + "Let people know how they can help - advice, resources, or a vibe check.", + icon: SparklesIcon, + }, + ]; + + const safetyChecklist = [ + { + title: "Protect identities", + description: + "Skip names, classes, or any info that could point to a specific person.", + icon: ShieldCheckIcon, + }, + { + title: "Keep it kind", + description: + "We moderate for empathy and respect - critique ideas, not people.", + icon: SparklesIcon, + }, + ]; + const mutation = useMutation({ mutationFn: (values: z.infer) => { return fetch("/api/posts", { @@ -68,95 +114,209 @@ export default function SubmitPage() { }); return ( -
{ - e.preventDefault(); - form.handleSubmit(); - }} - > - ( - + + + +
+
+ + Submit anonymously + +

+ Give your story the care it deserves. +

+

+ A thoughtful title and a little context go a long way. Your post is + only visible to other students, and your identity stays hidden. +

+
+
+ +
+ { + e.preventDefault(); + form.handleSubmit(); + }} + > + ( + + )} /> - )} - /> - - { - const selectedTags = field.state.value; - - return ( -
- - -
- {tags && selectedTags.length > 0 ? ( - selectedTags.map((tagId) => { - const tag = tags.find((t) => t.id === tagId); - if (!tag) return null; - - return ( - - {tag.name} - - - ); - }) - ) : ( - - No tags selected - - )} - -
-
- ); - }} - /> - - ( - { + const selectedTags = field.state.value; + + return ( +
+
+ + {selectedTags.length > 0 && ( + + )} +
+

+ Pick up to 3 tags so the right people find your post faster. +

+ +
+ {tags && selectedTags.length > 0 ? ( + selectedTags.map((tagId) => { + const tag = tags.find((t) => t.id === tagId); + if (!tag) return null; + + return ( + + {tag.name} + + + ); + }) + ) : ( + + No tags yet - add a few for quicker discovery. + + )} +
+ + {!tags && ( +

+ Fetching available tags... +

+ )} + + +
+ ); + }} /> - )} - /> - -
- - ( + + )} /> - + + +
+ + +
+
+ + +
- + ); } diff --git a/app/globals.css b/app/globals.css index 181792e..f0ae184 100644 --- a/app/globals.css +++ b/app/globals.css @@ -11,15 +11,15 @@ } :root { - --radius: 0.65rem; + --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.795 0.184 86.047); - --primary-foreground: oklch(0.421 0.095 57.708); + --primary: oklch(0.21 0.006 285.885); + --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.967 0.001 286.375); @@ -29,7 +29,7 @@ --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.795 0.184 86.047); + --ring: oklch(0.705 0.015 286.067); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -37,12 +37,12 @@ --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.795 0.184 86.047); - --sidebar-primary-foreground: oklch(0.421 0.095 57.708); + --sidebar-primary: oklch(0.21 0.006 285.885); + --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.795 0.184 86.047); + --sidebar-ring: oklch(0.705 0.015 286.067); } .dark { @@ -52,8 +52,8 @@ --card-foreground: oklch(0.985 0 0); --popover: oklch(0.21 0.006 285.885); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.795 0.184 86.047); - --primary-foreground: oklch(0.421 0.095 57.708); + --primary: oklch(0.92 0.004 286.32); + --primary-foreground: oklch(0.21 0.006 285.885); --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.274 0.006 286.033); @@ -63,7 +63,7 @@ --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); - --ring: oklch(0.554 0.135 66.442); + --ring: oklch(0.552 0.016 285.938); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); @@ -71,12 +71,12 @@ --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.795 0.184 86.047); - --sidebar-primary-foreground: oklch(0.421 0.095 57.708); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.554 0.135 66.442); + --sidebar-ring: oklch(0.552 0.016 285.938); } @theme inline { @@ -135,4 +135,32 @@ [role="button"]:not(:disabled) { cursor: pointer; } + + .shiny-text { + display: inline-block; + background-image: linear-gradient( + 120deg, + rgba(255, 255, 255, 0) 40%, + rgba(255, 255, 255, 0.8) 50%, + rgba(255, 255, 255, 0) 60% + ); + background-size: 200% 100%; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; /* ensure text shows the background gradient */ + color: transparent; + } + + .animate-shine { + animation: shine var(--shine-duration, 5s) linear infinite; + } + + @keyframes shine { + 0% { + background-position: 100%; + } + 100% { + background-position: -100%; + } + } } diff --git a/app/page.tsx b/app/page.tsx index a6d0e4d..b61a44c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,13 +1,30 @@ import { MessageSquareTextIcon } from "lucide-react"; import Link from "next/link"; +import DecryptedText from "@/components/DecryptedText"; +import { FlatMap } from "@/components/flat-map"; import { Footer } from "@/components/footer"; import { HighlightText } from "@/components/highlight-text"; +import LightRays from "@/components/LightRays"; import { Navbar } from "@/components/navbar"; +import ShinyText from "@/components/ShinyText"; import { Button } from "@/components/ui/button"; export default function Home() { return ( -
+
+
@@ -15,12 +32,19 @@ export default function Home() { umedu

- open-source, anonymous, encrypted, private edu forums + open-source, anonymous, + + , private edu forums

@@ -49,13 +73,22 @@ export default function Home() { email. No personal information is stored, ensuring your privacy is protected.{" "} - - Learn more → + + Learn more{" "} + + {" "} + → +
- +
+ +
); diff --git a/app/posts/[id]/components/post-date.tsx b/app/posts/[id]/components/post-date.tsx index 0c70db0..f31112d 100644 --- a/app/posts/[id]/components/post-date.tsx +++ b/app/posts/[id]/components/post-date.tsx @@ -1,11 +1,13 @@ "use client"; +import { CalendarIcon } from "lucide-react"; import { formatDate } from "@/lib/utils"; export function PostDate({ createdAt }: { createdAt: Date }) { return ( -

- Posted at {formatDate(createdAt)} -

+ + + {formatDate(createdAt)} + ); } diff --git a/app/posts/[id]/page.tsx b/app/posts/[id]/page.tsx index 59fc77c..d023e8b 100644 --- a/app/posts/[id]/page.tsx +++ b/app/posts/[id]/page.tsx @@ -1,12 +1,21 @@ import { eq } from "drizzle-orm"; +import { ArrowLeftIcon } from "lucide-react"; import type { Metadata } from "next"; import { unstable_cache } from "next/cache"; +import Link from "next/link"; import { notFound } from "next/navigation"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { ForumNavbar } from "@/app/forum/components/forum-navbar"; import { Footer } from "@/components/footer"; import { Badge } from "@/components/ui/badge"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { db } from "@/db"; import { postTable } from "@/db/schema"; @@ -55,33 +64,62 @@ export default async function Page({ params }: Props) { notFound(); } + const wordCount = post.content.trim().split(/\s+/).length; + const readingTime = Math.max(1, Math.round(wordCount / 180)); + return ( -
-
- } - /> -
-

{post.title}

- -
- {post.tags.map((tag) => ( - - {tag.name} - - ))} +
+ } + /> +
+ + + + +
+ + + Anonymous thread + + + {post.title} + + + {post.tags.map((tag) => ( + + {tag.name} + + ))} + + + + + + {wordCount.toLocaleString()} words + + + {readingTime} min read + + + + +
+
+ {post.content}
- - -
+ - -
- {post.content} -
-
+
diff --git a/bun.lock b/bun.lock index d216d58..3bdad1d 100644 --- a/bun.lock +++ b/bun.lock @@ -12,12 +12,13 @@ "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", - "@tanstack/react-form": "^1.23.8", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-form": "^1.25.0", "@tanstack/react-pacer": "^0.16.4", - "@tanstack/react-query": "^5.90.5", + "@tanstack/react-query": "^5.90.10", "@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-virtual": "^3.13.12", "arctic": "^3.7.0", @@ -27,16 +28,19 @@ "drizzle-orm": "^0.44.7", "drizzle-seed": "^0.3.1", "lucide-react": "^0.548.0", + "motion": "^12.23.24", "nanoid": "^5.1.6", "next": "15.5.6", "next-themes": "^0.4.6", + "ogl": "^1.0.11", "react": "^19.2.0", "react-dom": "^19.2.0", "react-intersection-observer": "^9.16.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1", + "svg-dotted-map": "^2.0.1", + "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2", "zod": "^4.1.12", @@ -44,14 +48,14 @@ "devDependencies": { "@biomejs/biome": "2.3.1", "@faker-js/faker": "^10.1.0", - "@tailwindcss/postcss": "^4.1.16", + "@tailwindcss/postcss": "^4.1.17", "@tailwindcss/typography": "^0.5.19", - "@types/node": "^22.18.12", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", + "@types/node": "^22.19.1", + "@types/react": "^19.2.6", + "@types/react-dom": "^19.2.3", "drizzle-kit": "0.31.5", - "lefthook": "^2.0.1", - "tailwindcss": "^4.1.16", + "lefthook": "^2.0.4", + "tailwindcss": "^4.1.17", "typescript": "^5.9.3", }, }, @@ -297,7 +301,7 @@ "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="], "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], @@ -311,9 +315,11 @@ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], @@ -331,57 +337,59 @@ "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], - "@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="], + "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="], - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.16", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "postcss": "^8.4.41", "tailwindcss": "4.1.16" } }, "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A=="], + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", "tailwindcss": "4.1.17" } }, "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw=="], "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], - "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.3.3", "", {}, "sha512-RfV+OPV/M3CGryYqTue684u10jUt55PEqeBOnOtCe6tAmHI9Iqyc8nHeDhWPEV9715gShuauFVaMc9RiUVNdwg=="], + "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.3.5", "", {}, "sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw=="], - "@tanstack/form-core": ["@tanstack/form-core@1.24.4", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.3", "@tanstack/pacer": "^0.15.3", "@tanstack/store": "^0.7.7" } }, "sha512-+eIR7DiDamit1zvTVgaHxuIRA02YFgJaXMUGxsLRJoBpUjGl/g/nhUocQoNkRyfXqOlh8OCMTanjwDprWSRq6w=="], + "@tanstack/form-core": ["@tanstack/form-core@1.25.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.5", "@tanstack/pacer": "^0.15.3", "@tanstack/store": "^0.7.7" } }, "sha512-OEWW2uTOFMyRmHrVEiPOn+J27ekQ/vXwRAJt9kD8U8vCt8CmpClj989OOGGSBSVJtDNxGUcWyKF8gYznnqIyaw=="], "@tanstack/pacer": ["@tanstack/pacer@0.15.4", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.2", "@tanstack/store": "^0.7.5" } }, "sha512-vGY+CWsFZeac3dELgB6UZ4c7OacwsLb8hvL2gLS6hTgy8Fl0Bm/aLokHaeDIP+q9F9HUZTnp360z9uv78eg8pg=="], - "@tanstack/query-core": ["@tanstack/query-core@5.90.5", "", {}, "sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w=="], + "@tanstack/query-core": ["@tanstack/query-core@5.90.10", "", {}, "sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ=="], "@tanstack/query-devtools": ["@tanstack/query-devtools@5.90.1", "", {}, "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ=="], - "@tanstack/react-form": ["@tanstack/react-form@1.23.8", "", { "dependencies": { "@tanstack/form-core": "1.24.4", "@tanstack/react-store": "^0.7.7", "decode-formdata": "^0.9.0", "devalue": "^5.3.2" }, "peerDependencies": { "@tanstack/react-start": "^1.130.10", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@tanstack/react-start"] }, "sha512-ivfkiOHAI3aIWkCY4FnPWVAL6SkQWGWNVjtwIZpaoJE4ulukZWZ1KB8TQKs8f4STl+egjTsMHrWJuf2fv3Xh1w=="], + "@tanstack/react-form": ["@tanstack/react-form@1.25.0", "", { "dependencies": { "@tanstack/form-core": "1.25.0", "@tanstack/react-store": "^0.7.7" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-SjKpBkjegNVW9WU+qlO8+/+kSbSEwo2zwHnrQz/yOnnJRhtdgubUt50LfeUtdzkMsbbptQ5MSZrXH03kidQjyw=="], "@tanstack/react-pacer": ["@tanstack/react-pacer@0.16.4", "", { "dependencies": { "@tanstack/pacer": "0.15.4", "@tanstack/react-store": "^0.7.5" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-nuQLE8bx0rYMiJau4jOTPZFp3XC/GnIHDKfKVVWeKUHNF4grRdVHPgTlJ8EV/nt/HJxSUnIcy+IIKX+Bj0bLSw=="], - "@tanstack/react-query": ["@tanstack/react-query@5.90.5", "", { "dependencies": { "@tanstack/query-core": "5.90.5" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.10", "", { "dependencies": { "@tanstack/query-core": "5.90.10" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw=="], "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.90.2", "", { "dependencies": { "@tanstack/query-devtools": "5.90.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.2", "react": "^18 || ^19" } }, "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ=="], @@ -405,11 +413,11 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@22.18.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog=="], + "@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="], - "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + "@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="], - "@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="], + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -455,7 +463,7 @@ "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], @@ -463,8 +471,6 @@ "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "decode-formdata": ["decode-formdata@0.9.0", "", {}, "sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw=="], - "decode-named-character-reference": ["decode-named-character-reference@1.1.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w=="], "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], @@ -473,8 +479,6 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - "devalue": ["devalue@5.3.2", "", {}, "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw=="], - "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "drizzle-kit": ["drizzle-kit@0.31.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-+CHgPFzuoTQTt7cOYCV6MOw2w8vqEn/ap1yv4bpZOWL03u7rlVRQhUY0WYT3rHsgVTXwYQDZaSUJSQrMBUKuWg=="], @@ -499,6 +503,8 @@ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "framer-motion": ["framer-motion@12.23.24", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w=="], + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], @@ -529,27 +535,27 @@ "js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="], - "lefthook": ["lefthook@2.0.1", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.0.1", "lefthook-darwin-x64": "2.0.1", "lefthook-freebsd-arm64": "2.0.1", "lefthook-freebsd-x64": "2.0.1", "lefthook-linux-arm64": "2.0.1", "lefthook-linux-x64": "2.0.1", "lefthook-openbsd-arm64": "2.0.1", "lefthook-openbsd-x64": "2.0.1", "lefthook-windows-arm64": "2.0.1", "lefthook-windows-x64": "2.0.1" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-3jL1AmEnjchHyFL9GzBaRVcfcPTQLUtXawaF6Y6MXPPCSbirTh8q/is+Ijbd1zn0FA5MwQDdSYm0guVXUkeVWg=="], + "lefthook": ["lefthook@2.0.4", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.0.4", "lefthook-darwin-x64": "2.0.4", "lefthook-freebsd-arm64": "2.0.4", "lefthook-freebsd-x64": "2.0.4", "lefthook-linux-arm64": "2.0.4", "lefthook-linux-x64": "2.0.4", "lefthook-openbsd-arm64": "2.0.4", "lefthook-openbsd-x64": "2.0.4", "lefthook-windows-arm64": "2.0.4", "lefthook-windows-x64": "2.0.4" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-GNCU2vQWM/UWjiEF23601aILi1aMbPke6viortH7wIO/oVGOCW0H6FdLez4XZDyqnHL9XkTnd0BBVrBbYVMLpA=="], - "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.0.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-en5tDduXeltmlbBR/wECkwkILpghR9LxexeBuHWwyTZnOunm3bk4XGg9WKwT1sWlMaKJiXl7Tv0dUB/K1d3ajg=="], + "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AR63/O5UkM7Sc6x5PhP4vTuztTYRBeBroXApeWGM/8e5uZyoQug/7KTh7xhbCMDf8WJv6vdFeXAQCPSmDyPU3Q=="], - "lefthook-darwin-x64": ["lefthook-darwin-x64@2.0.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-upD7B5kK3/sAqUKWfcejvWwXmBfu4LK+0bwUZWHE0cuPsn9KEZ8zG0Vw7JSBhuSZ8LeZA8PudlmQXHvR3YjDew=="], + "lefthook-darwin-x64": ["lefthook-darwin-x64@2.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-618DVUttSzV9egQiqTQoxGfnR240JoPWYmqRVHhiegnQKZ2lp5XJ+7NMxeRk/ih93VVOLzFO5ky3PbpxTmJgjQ=="], - "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@2.0.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-DqraAmqgSOllSzRjhjjaToswhpz0WWfnTo7HPws5BYOLghQJ/qErwXOXs2I76YqbHgXr831wtgkamQOVOfR0Jg=="], + "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@2.0.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mTAQym1BK38fKglHBQ/0GXPznVC4LoStHO5lAI3ZxaEC0FQetqGHYFzhWbIH5sde9JhztE2rL/aBzMHDoAtzSw=="], - "lefthook-freebsd-x64": ["lefthook-freebsd-x64@2.0.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kdLmXvgWvSkFLBSIZGevkPEqoOwQIaHAtcXpbfn0unbsgqLG+u4V7Bnp73BfBUkc0ER+72W9gUas9HZALzTjEg=="], + "lefthook-freebsd-x64": ["lefthook-freebsd-x64@2.0.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sy02aSxd8UMd6XmiPFVl/Em0b78jdZcDSsLwg+bweJQQk0l+vJhOfqFiG11mbnpo+EBIZmRe6OH5LkxeSU36+w=="], - "lefthook-linux-arm64": ["lefthook-linux-arm64@2.0.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-pEQomhTh0SSeIv3G5337efm1BHQCBSrewYnUydkLFLMwkqzVzIW2119pk5bMNSKFQ2xoRs3AKww+odTgsQiLcQ=="], + "lefthook-linux-arm64": ["lefthook-linux-arm64@2.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-W0Nlr/Cz2QTH9n4k5zNrk3LSsg1C4wHiJi8hrAiQVTaAV/N1XrKqd0DevqQuouuapG6pw/6B1xCgiNPebv9oyw=="], - "lefthook-linux-x64": ["lefthook-linux-x64@2.0.1", "", { "os": "linux", "cpu": "x64" }, "sha512-8tOmdiUU/awCoW/PJ25QmhHuHXUbDQ9BF3tADYuxgGSSCLtvx1Zvr+F4pyB8jCOAPvsx9XAt3UM3n8/nFXKYcw=="], + "lefthook-linux-x64": ["lefthook-linux-x64@2.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-N6ySVCtB/DrOZ1ZgPL8WBZTgtoVHvcPKI+LV5wbcGrvA/dzDZFvniadrbDWZg7Tm705efiQzyENjwhhqNkwiww=="], - "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@2.0.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-SBOMOjkyM7bQTvzTlnNS5PzfhJigZJYdmdcJ/H4EadJfxl+72v0YG9SHY64AdfuvZUB39AvaBD6UothZCtLuEQ=="], + "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@2.0.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-VmOhJO3pYzZ/1C2WFXtL/n5pq4/eYOroqJJpwTJfmCHyw4ceLACu8MDyU5AMJhGMkbL8mPxGInJKxg5xhYgGRw=="], - "lefthook-openbsd-x64": ["lefthook-openbsd-x64@2.0.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-22E1V889hx1rMOxgGFUc0ujiOAcM7kX0x7bCK6rfXFsqYl30X7A+4hj2JTwxRxSTb7ZkaiSFF64UE7iGi/f4AA=="], + "lefthook-openbsd-x64": ["lefthook-openbsd-x64@2.0.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-U8MZz1xlHUdflkQQ2hkMQsei6fSZbs8tuE4EjCIHWnNdnAF4V8sZ6n1KbxsJcoZXPyBZqxZSMu1o/Ye8IAMVKg=="], - "lefthook-windows-arm64": ["lefthook-windows-arm64@2.0.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-0dDrCj0AatmJj5voABZ/7H27eZiaa1CVtTFYfKDRJj2+acEo3F7x/iadknftMqfjOjgjZpmRwSx49NWA2vMQjg=="], + "lefthook-windows-arm64": ["lefthook-windows-arm64@2.0.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-543H3y2JAwNdvwUQ6nlNBG7rdKgoOUgzAa6pYcl6EoqicCRrjRmGhkJu7vUudkkrD2Wjm7tr9hU9poP2g5fRFQ=="], - "lefthook-windows-x64": ["lefthook-windows-x64@2.0.1", "", { "os": "win32", "cpu": "x64" }, "sha512-vmbqUY7lsgYiDG9b9oc0zP9b/pHx6YpS310EBv6YkVEf8mseNRKRYwm6QqvOJ919rrmZfQpOFs5W+5O9K7zleg=="], + "lefthook-windows-x64": ["lefthook-windows-x64@2.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UDEPK9RWKm60xsNOdS/DQOdFba0SFa4w3tpFMXK1AJzmRHhosoKrorXGhtTr6kcM0MGKOtYi8GHsm++ArZ9wvQ=="], "libsql": ["libsql@0.5.22", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.22", "@libsql/darwin-x64": "0.5.22", "@libsql/linux-arm-gnueabihf": "0.5.22", "@libsql/linux-arm-musleabihf": "0.5.22", "@libsql/linux-arm64-gnu": "0.5.22", "@libsql/linux-arm64-musl": "0.5.22", "@libsql/linux-x64-gnu": "0.5.22", "@libsql/linux-x64-musl": "0.5.22", "@libsql/win32-x64-msvc": "0.5.22" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA=="], @@ -581,7 +587,7 @@ "lucide-react": ["lucide-react@0.548.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA=="], - "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], @@ -671,6 +677,12 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + "motion": ["motion@12.23.24", "", { "dependencies": { "framer-motion": "^12.23.24", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw=="], + + "motion-dom": ["motion-dom@12.23.23", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA=="], + + "motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], @@ -683,6 +695,8 @@ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "ogl": ["ogl@1.0.11", "", {}, "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -747,9 +761,11 @@ "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], - "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], + "svg-dotted-map": ["svg-dotted-map@2.0.1", "", {}, "sha512-eeI2XzIKm23gmSVr7ASTMNVJvxAvBfyL30tN33Y/DcZCJXvC/Br/cxQp9Ts6jDK/e7fkE5TpZStEfduPqPXrIw=="], + + "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], - "tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="], + "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], @@ -807,9 +823,23 @@ "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], + "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + + "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], @@ -879,6 +909,10 @@ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], + "@types/ws/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "next/postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], @@ -890,5 +924,7 @@ "vaul/@radix-ui/react-dialog/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA=="], "vaul/@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], + + "vaul/@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], } } diff --git a/components.json b/components.json index a64445d..f54adcf 100644 --- a/components.json +++ b/components.json @@ -10,6 +10,7 @@ "cssVariables": true, "prefix": "" }, + "iconLibrary": "lucide", "aliases": { "components": "@/components", "utils": "@/lib/utils", @@ -17,5 +18,7 @@ "lib": "@/lib", "hooks": "@/hooks" }, - "iconLibrary": "lucide" + "registries": { + "@magicui": "https://magicui.design/r/{name}.json" + } } diff --git a/components/DecryptedText.tsx b/components/DecryptedText.tsx new file mode 100644 index 0000000..c5ae637 --- /dev/null +++ b/components/DecryptedText.tsx @@ -0,0 +1,242 @@ +"use client"; + +import { type HTMLMotionProps, motion } from "motion/react"; +import { useEffect, useRef, useState } from "react"; + +interface DecryptedTextProps extends HTMLMotionProps<"span"> { + text: string; + speed?: number; + maxIterations?: number; + sequential?: boolean; + revealDirection?: "start" | "end" | "center"; + useOriginalCharsOnly?: boolean; + characters?: string; + className?: string; + encryptedClassName?: string; + parentClassName?: string; + animateOn?: "view" | "hover" | "both"; +} + +export default function DecryptedText({ + text, + speed = 50, + maxIterations = 10, + sequential = false, + revealDirection = "start", + useOriginalCharsOnly = false, + characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&*()_+", + className = "", + parentClassName = "", + encryptedClassName = "", + animateOn = "hover", + ...props +}: DecryptedTextProps) { + const [displayText, setDisplayText] = useState(text); + const [isHovering, setIsHovering] = useState(false); + const [isScrambling, setIsScrambling] = useState(false); + const [revealedIndices, setRevealedIndices] = useState>( + new Set(), + ); + const [hasAnimated, setHasAnimated] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + let interval: NodeJS.Timeout; + let currentIteration = 0; + + const getNextIndex = (revealedSet: Set): number => { + const textLength = text.length; + switch (revealDirection) { + case "start": + return revealedSet.size; + case "end": + return textLength - 1 - revealedSet.size; + case "center": { + const middle = Math.floor(textLength / 2); + const offset = Math.floor(revealedSet.size / 2); + const nextIndex = + revealedSet.size % 2 === 0 ? middle + offset : middle - offset - 1; + + if ( + nextIndex >= 0 && + nextIndex < textLength && + !revealedSet.has(nextIndex) + ) { + return nextIndex; + } + for (let i = 0; i < textLength; i++) { + if (!revealedSet.has(i)) return i; + } + return 0; + } + default: + return revealedSet.size; + } + }; + + const availableChars = useOriginalCharsOnly + ? Array.from(new Set(text.split(""))).filter((char) => char !== " ") + : characters.split(""); + + const shuffleText = ( + originalText: string, + currentRevealed: Set, + ): string => { + if (useOriginalCharsOnly) { + const positions = originalText.split("").map((char, i) => ({ + char, + isSpace: char === " ", + index: i, + isRevealed: currentRevealed.has(i), + })); + + const nonSpaceChars = positions + .filter((p) => !p.isSpace && !p.isRevealed) + .map((p) => p.char); + + for (let i = nonSpaceChars.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [nonSpaceChars[i], nonSpaceChars[j]] = [ + nonSpaceChars[j], + nonSpaceChars[i], + ]; + } + + let charIndex = 0; + return positions + .map((p) => { + if (p.isSpace) return " "; + if (p.isRevealed) return originalText[p.index]; + return nonSpaceChars[charIndex++]; + }) + .join(""); + } else { + return originalText + .split("") + .map((char, i) => { + if (char === " ") return " "; + if (currentRevealed.has(i)) return originalText[i]; + return availableChars[ + Math.floor(Math.random() * availableChars.length) + ]; + }) + .join(""); + } + }; + + if (isHovering) { + setIsScrambling(true); + interval = setInterval(() => { + setRevealedIndices((prevRevealed) => { + if (sequential) { + if (prevRevealed.size < text.length) { + const nextIndex = getNextIndex(prevRevealed); + const newRevealed = new Set(prevRevealed); + newRevealed.add(nextIndex); + setDisplayText(shuffleText(text, newRevealed)); + return newRevealed; + } else { + clearInterval(interval); + setIsScrambling(false); + return prevRevealed; + } + } else { + setDisplayText(shuffleText(text, prevRevealed)); + currentIteration++; + if (currentIteration >= maxIterations) { + clearInterval(interval); + setIsScrambling(false); + setDisplayText(text); + } + return prevRevealed; + } + }); + }, speed); + } else { + setDisplayText(text); + setRevealedIndices(new Set()); + setIsScrambling(false); + } + + return () => { + if (interval) clearInterval(interval); + }; + }, [ + isHovering, + text, + speed, + maxIterations, + sequential, + revealDirection, + characters, + useOriginalCharsOnly, + ]); + + useEffect(() => { + if (animateOn !== "view" && animateOn !== "both") return; + + const observerCallback = (entries: IntersectionObserverEntry[]) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !hasAnimated) { + setIsHovering(true); + setHasAnimated(true); + } + }); + }; + + const observerOptions = { + root: null, + rootMargin: "0px", + threshold: 0.1, + }; + + const observer = new IntersectionObserver( + observerCallback, + observerOptions, + ); + const currentRef = containerRef.current; + if (currentRef) { + observer.observe(currentRef); + } + + return () => { + if (currentRef) observer.unobserve(currentRef); + }; + }, [animateOn, hasAnimated]); + + const hoverProps = + animateOn === "hover" || animateOn === "both" + ? { + onMouseEnter: () => setIsHovering(true), + onMouseLeave: () => setIsHovering(false), + } + : {}; + + return ( + + {displayText} + + + + ); +} diff --git a/components/LightRays.tsx b/components/LightRays.tsx new file mode 100644 index 0000000..208530e --- /dev/null +++ b/components/LightRays.tsx @@ -0,0 +1,450 @@ +"use client"; + +import { Mesh, Program, Renderer, Triangle } from "ogl"; +import { useEffect, useRef, useState } from "react"; + +export type RaysOrigin = + | "top-center" + | "top-left" + | "top-right" + | "right" + | "left" + | "bottom-center" + | "bottom-right" + | "bottom-left"; + +interface LightRaysProps { + raysOrigin?: RaysOrigin; + raysColor?: string; + raysSpeed?: number; + lightSpread?: number; + rayLength?: number; + pulsating?: boolean; + fadeDistance?: number; + saturation?: number; + followMouse?: boolean; + mouseInfluence?: number; + noiseAmount?: number; + distortion?: number; + className?: string; +} + +const DEFAULT_COLOR = "#ffffff"; + +const hexToRgb = (hex: string): [number, number, number] => { + const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return m + ? [ + parseInt(m[1], 16) / 255, + parseInt(m[2], 16) / 255, + parseInt(m[3], 16) / 255, + ] + : [1, 1, 1]; +}; + +const getAnchorAndDir = ( + origin: RaysOrigin, + w: number, + h: number, +): { anchor: [number, number]; dir: [number, number] } => { + const outside = 0.2; + switch (origin) { + case "top-left": + return { anchor: [0, -outside * h], dir: [0, 1] }; + case "top-right": + return { anchor: [w, -outside * h], dir: [0, 1] }; + case "left": + return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] }; + case "right": + return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] }; + case "bottom-left": + return { anchor: [0, (1 + outside) * h], dir: [0, -1] }; + case "bottom-center": + return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] }; + case "bottom-right": + return { anchor: [w, (1 + outside) * h], dir: [0, -1] }; + default: // "top-center" + return { anchor: [0.5 * w, -outside * h], dir: [0, 1] }; + } +}; + +const LightRays: React.FC = ({ + raysOrigin = "top-center", + raysColor = DEFAULT_COLOR, + raysSpeed = 1, + lightSpread = 1, + rayLength = 2, + pulsating = false, + fadeDistance = 1.0, + saturation = 1.0, + followMouse = true, + mouseInfluence = 0.1, + noiseAmount = 0.0, + distortion = 0.0, + className = "", +}) => { + const containerRef = useRef(null); + const uniformsRef = useRef(null); + const rendererRef = useRef(null); + const mouseRef = useRef({ x: 0.5, y: 0.5 }); + const smoothMouseRef = useRef({ x: 0.5, y: 0.5 }); + const animationIdRef = useRef(null); + const meshRef = useRef(null); + const cleanupFunctionRef = useRef<(() => void) | null>(null); + const [isVisible, setIsVisible] = useState(false); + const observerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + + observerRef.current = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + setIsVisible(entry.isIntersecting); + }, + { threshold: 0.1 }, + ); + + observerRef.current.observe(containerRef.current); + + return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } + }; + }, []); + + useEffect(() => { + if (!isVisible || !containerRef.current) return; + + if (cleanupFunctionRef.current) { + cleanupFunctionRef.current(); + cleanupFunctionRef.current = null; + } + + const initializeWebGL = async () => { + if (!containerRef.current) return; + + await new Promise((resolve) => setTimeout(resolve, 10)); + + if (!containerRef.current) return; + + const renderer = new Renderer({ + dpr: Math.min(window.devicePixelRatio, 2), + alpha: true, + }); + rendererRef.current = renderer; + + const gl = renderer.gl; + gl.canvas.style.width = "100%"; + gl.canvas.style.height = "100%"; + + while (containerRef.current.firstChild) { + containerRef.current.removeChild(containerRef.current.firstChild); + } + containerRef.current.appendChild(gl.canvas); + + const vert = ` +attribute vec2 position; +varying vec2 vUv; +void main() { + vUv = position * 0.5 + 0.5; + gl_Position = vec4(position, 0.0, 1.0); +}`; + + const frag = `precision highp float; + +uniform float iTime; +uniform vec2 iResolution; + +uniform vec2 rayPos; +uniform vec2 rayDir; +uniform vec3 raysColor; +uniform float raysSpeed; +uniform float lightSpread; +uniform float rayLength; +uniform float pulsating; +uniform float fadeDistance; +uniform float saturation; +uniform vec2 mousePos; +uniform float mouseInfluence; +uniform float noiseAmount; +uniform float distortion; + +varying vec2 vUv; + +float noise(vec2 st) { + return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123); +} + +float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, + float seedA, float seedB, float speed) { + vec2 sourceToCoord = coord - raySource; + vec2 dirNorm = normalize(sourceToCoord); + float cosAngle = dot(dirNorm, rayRefDirection); + + float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2; + + float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001)); + + float distance = length(sourceToCoord); + float maxDistance = iResolution.x * rayLength; + float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0); + + float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0); + float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0; + + float baseStrength = clamp( + (0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) + + (0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)), + 0.0, 1.0 + ); + + return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y); + + vec2 finalRayDir = rayDir; + if (mouseInfluence > 0.0) { + vec2 mouseScreenPos = mousePos * iResolution.xy; + vec2 mouseDirection = normalize(mouseScreenPos - rayPos); + finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence)); + } + + vec4 rays1 = vec4(1.0) * + rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349, + 1.5 * raysSpeed); + vec4 rays2 = vec4(1.0) * + rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234, + 1.1 * raysSpeed); + + fragColor = rays1 * 0.5 + rays2 * 0.4; + + if (noiseAmount > 0.0) { + float n = noise(coord * 0.01 + iTime * 0.1); + fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n); + } + + float brightness = 1.0 - (coord.y / iResolution.y); + fragColor.x *= 0.1 + brightness * 0.8; + fragColor.y *= 0.3 + brightness * 0.6; + fragColor.z *= 0.5 + brightness * 0.5; + + if (saturation != 1.0) { + float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114)); + fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation); + } + + fragColor.rgb *= raysColor; +} + +void main() { + vec4 color; + mainImage(color, gl_FragCoord.xy); + gl_FragColor = color; +}`; + + const uniforms = { + iTime: { value: 0 }, + iResolution: { value: [1, 1] }, + + rayPos: { value: [0, 0] }, + rayDir: { value: [0, 1] }, + + raysColor: { value: hexToRgb(raysColor) }, + raysSpeed: { value: raysSpeed }, + lightSpread: { value: lightSpread }, + rayLength: { value: rayLength }, + pulsating: { value: pulsating ? 1.0 : 0.0 }, + fadeDistance: { value: fadeDistance }, + saturation: { value: saturation }, + mousePos: { value: [0.5, 0.5] }, + mouseInfluence: { value: mouseInfluence }, + noiseAmount: { value: noiseAmount }, + distortion: { value: distortion }, + }; + uniformsRef.current = uniforms; + + const geometry = new Triangle(gl); + const program = new Program(gl, { + vertex: vert, + fragment: frag, + uniforms, + }); + const mesh = new Mesh(gl, { geometry, program }); + meshRef.current = mesh; + + const updatePlacement = () => { + if (!containerRef.current || !renderer) return; + + renderer.dpr = Math.min(window.devicePixelRatio, 2); + + const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current; + renderer.setSize(wCSS, hCSS); + + const dpr = renderer.dpr; + const w = wCSS * dpr; + const h = hCSS * dpr; + + uniforms.iResolution.value = [w, h]; + + const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h); + uniforms.rayPos.value = anchor; + uniforms.rayDir.value = dir; + }; + + const loop = (t: number) => { + if (!rendererRef.current || !uniformsRef.current || !meshRef.current) { + return; + } + + uniforms.iTime.value = t * 0.001; + + if (followMouse && mouseInfluence > 0.0) { + const smoothing = 0.92; + + smoothMouseRef.current.x = + smoothMouseRef.current.x * smoothing + + mouseRef.current.x * (1 - smoothing); + smoothMouseRef.current.y = + smoothMouseRef.current.y * smoothing + + mouseRef.current.y * (1 - smoothing); + + uniforms.mousePos.value = [ + smoothMouseRef.current.x, + smoothMouseRef.current.y, + ]; + } + + try { + renderer.render({ scene: mesh }); + animationIdRef.current = requestAnimationFrame(loop); + } catch (error) { + console.warn("WebGL rendering error:", error); + return; + } + }; + + window.addEventListener("resize", updatePlacement); + updatePlacement(); + animationIdRef.current = requestAnimationFrame(loop); + + cleanupFunctionRef.current = () => { + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current); + animationIdRef.current = null; + } + + window.removeEventListener("resize", updatePlacement); + + if (renderer) { + try { + const canvas = renderer.gl.canvas; + const loseContextExt = + renderer.gl.getExtension("WEBGL_lose_context"); + if (loseContextExt) { + loseContextExt.loseContext(); + } + + if (canvas && canvas.parentNode) { + canvas.parentNode.removeChild(canvas); + } + } catch (error) { + console.warn("Error during WebGL cleanup:", error); + } + } + + rendererRef.current = null; + uniformsRef.current = null; + meshRef.current = null; + }; + }; + + initializeWebGL(); + + return () => { + if (cleanupFunctionRef.current) { + cleanupFunctionRef.current(); + cleanupFunctionRef.current = null; + } + }; + }, [ + isVisible, + raysOrigin, + raysColor, + raysSpeed, + lightSpread, + rayLength, + pulsating, + fadeDistance, + saturation, + followMouse, + mouseInfluence, + noiseAmount, + distortion, + ]); + + useEffect(() => { + if (!uniformsRef.current || !containerRef.current || !rendererRef.current) + return; + + const u = uniformsRef.current; + const renderer = rendererRef.current; + + u.raysColor.value = hexToRgb(raysColor); + u.raysSpeed.value = raysSpeed; + u.lightSpread.value = lightSpread; + u.rayLength.value = rayLength; + u.pulsating.value = pulsating ? 1.0 : 0.0; + u.fadeDistance.value = fadeDistance; + u.saturation.value = saturation; + u.mouseInfluence.value = mouseInfluence; + u.noiseAmount.value = noiseAmount; + u.distortion.value = distortion; + + const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current; + const dpr = renderer.dpr; + const { anchor, dir } = getAnchorAndDir(raysOrigin, wCSS * dpr, hCSS * dpr); + u.rayPos.value = anchor; + u.rayDir.value = dir; + }, [ + raysColor, + raysSpeed, + lightSpread, + raysOrigin, + rayLength, + pulsating, + fadeDistance, + saturation, + mouseInfluence, + noiseAmount, + distortion, + ]); + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!containerRef.current || !rendererRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + mouseRef.current = { x, y }; + }; + + if (followMouse) { + window.addEventListener("mousemove", handleMouseMove); + return () => window.removeEventListener("mousemove", handleMouseMove); + } + }, [followMouse]); + + return ( +
+ ); +}; + +export default LightRays; diff --git a/components/ShinyText.tsx b/components/ShinyText.tsx new file mode 100644 index 0000000..26e0fb9 --- /dev/null +++ b/components/ShinyText.tsx @@ -0,0 +1,34 @@ +import type React from "react"; + +interface ShinyTextProps { + text: string; + disabled?: boolean; + duration?: number; + className?: string; +} + +const ShinyText: React.FC = ({ + text, + disabled = false, + duration = 5, + className = "", +}) => { + const animationDuration = `${duration}s`; + + return ( +
+ {text} +
+ ); +}; + +export default ShinyText; diff --git a/components/dock-nav.tsx b/components/dock-nav.tsx new file mode 100644 index 0000000..31eb46e --- /dev/null +++ b/components/dock-nav.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { HomeIcon, InfoIcon, MessageCirclePlusIcon } from "lucide-react"; +import Link from "next/link"; +import type React from "react"; +import { LogoutButton } from "@/app/forum/components/logout-button"; +import { buttonVariants } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { Dock, DockIcon } from "./ui/dock"; + +export type IconProps = React.HTMLAttributes; + +const Icons = { + github: (props: IconProps) => ( + + + + ), +}; + +const DATA = { + navbar: [ + { href: "/", icon: HomeIcon, label: "Home" }, + { href: "/about", icon: InfoIcon, label: "About" }, + { + href: "/forum/submit", + icon: MessageCirclePlusIcon, + label: "Create Post", + }, + ], + secondary: { + Source: { + name: "Source code", + url: "https://github.com/omsimos/umedu", + icon: Icons.github, + }, + }, +}; + +export function DockNav({ logout }: { logout: () => Promise }) { + return ( +
+ + + {DATA.navbar.map((item) => ( + + + + + + + + +

{item.label}

+
+
+
+ ))} + + {Object.entries(DATA.secondary).map(([name, social]) => ( + + + + + + + + +

{name}

+
+
+
+ ))} + + + +
+ + +
+ +

Logout

+
+
+
+
+
+
+ ); +} diff --git a/components/flat-map.tsx b/components/flat-map.tsx new file mode 100644 index 0000000..4e24b12 --- /dev/null +++ b/components/flat-map.tsx @@ -0,0 +1,88 @@ +import { DottedMap } from "./ui/dotted-map"; + +const markers = [ + { + lat: 40.7128, + lng: -74.006, + size: 0.3, + }, // New York + { + lat: 34.0522, + lng: -118.2437, + size: 0.3, + }, // Los Angeles + { + lat: 51.5074, + lng: -0.1278, + size: 0.3, + }, // London + { + lat: -33.8688, + lng: 151.2093, + size: 0.3, + }, // Sydney + { + lat: 48.8566, + lng: 2.3522, + size: 0.3, + }, // Paris + { + lat: 35.6762, + lng: 139.6503, + size: 0.3, + }, // Tokyo + { + lat: 55.7558, + lng: 37.6176, + size: 0.3, + }, // Moscow + { + lat: 39.9042, + lng: 116.4074, + size: 0.3, + }, // Beijing + { + lat: 28.6139, + lng: 77.209, + size: 0.3, + }, // New Delhi + { + lat: -23.5505, + lng: -46.6333, + size: 0.3, + }, // São Paulo + { + lat: 1.3521, + lng: 103.8198, + size: 0.3, + }, // Singapore + { + lat: 25.2048, + lng: 55.2708, + size: 0.3, + }, // Dubai + { + lat: 52.52, + lng: 13.405, + size: 0.3, + }, // Berlin + { + lat: 19.4326, + lng: -99.1332, + size: 0.3, + }, // Mexico City + { + lat: -26.2041, + lng: 28.0473, + size: 0.3, + }, // Johannesburg +]; + +export function FlatMap() { + return ( +
+
+ +
+ ); +} diff --git a/components/footer.tsx b/components/footer.tsx index 71550b3..f5115ea 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -8,8 +8,8 @@ export function Footer() { umedu {" "} by{" "} - - @josh.xfi + + @omsimos
diff --git a/components/ui/dock.tsx b/components/ui/dock.tsx new file mode 100644 index 0000000..29eaef8 --- /dev/null +++ b/components/ui/dock.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { cva, type VariantProps } from "class-variance-authority"; +import type { MotionProps } from "motion/react"; +import { + type MotionValue, + motion, + useMotionValue, + useSpring, + useTransform, +} from "motion/react"; +import React, { type PropsWithChildren, useRef } from "react"; + +import { cn } from "@/lib/utils"; + +export interface DockProps extends VariantProps { + className?: string; + iconSize?: number; + iconMagnification?: number; + disableMagnification?: boolean; + iconDistance?: number; + direction?: "top" | "middle" | "bottom"; + children: React.ReactNode; +} + +const DEFAULT_SIZE = 40; +const DEFAULT_MAGNIFICATION = 60; +const DEFAULT_DISTANCE = 140; +const DEFAULT_DISABLEMAGNIFICATION = false; + +const dockVariants = cva( + "supports-backdrop-blur:bg-white/10 supports-backdrop-blur:dark:bg-black/10 mx-auto mt-8 flex h-[58px] w-max items-center justify-center gap-2 rounded-2xl border p-2 backdrop-blur-md", +); + +const Dock = React.forwardRef( + ( + { + className, + children, + iconSize = DEFAULT_SIZE, + iconMagnification = DEFAULT_MAGNIFICATION, + disableMagnification = DEFAULT_DISABLEMAGNIFICATION, + iconDistance = DEFAULT_DISTANCE, + direction = "middle", + ...props + }, + ref, + ) => { + const mouseX = useMotionValue(Infinity); + + const renderChildren = () => { + return React.Children.map(children, (child) => { + if ( + React.isValidElement(child) && + child.type === DockIcon + ) { + return React.cloneElement(child, { + ...child.props, + mouseX: mouseX, + size: iconSize, + magnification: iconMagnification, + disableMagnification: disableMagnification, + distance: iconDistance, + }); + } + return child; + }); + }; + + return ( + mouseX.set(e.pageX)} + onMouseLeave={() => mouseX.set(Infinity)} + {...props} + className={cn(dockVariants({ className }), { + "items-start": direction === "top", + "items-center": direction === "middle", + "items-end": direction === "bottom", + })} + > + {renderChildren()} + + ); + }, +); + +Dock.displayName = "Dock"; + +export interface DockIconProps + extends Omit, "children"> { + size?: number; + magnification?: number; + disableMagnification?: boolean; + distance?: number; + mouseX?: MotionValue; + className?: string; + children?: React.ReactNode; + props?: PropsWithChildren; +} + +const DockIcon = ({ + size = DEFAULT_SIZE, + magnification = DEFAULT_MAGNIFICATION, + disableMagnification, + distance = DEFAULT_DISTANCE, + mouseX, + className, + children, + ...props +}: DockIconProps) => { + const ref = useRef(null); + const padding = Math.max(6, size * 0.2); + const defaultMouseX = useMotionValue(Infinity); + + const distanceCalc = useTransform(mouseX ?? defaultMouseX, (val: number) => { + const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; + return val - bounds.x - bounds.width / 2; + }); + + const targetSize = disableMagnification ? size : magnification; + + const sizeTransform = useTransform( + distanceCalc, + [-distance, 0, distance], + [size, targetSize, size], + ); + + const scaleSize = useSpring(sizeTransform, { + mass: 0.1, + stiffness: 150, + damping: 12, + }); + + return ( + +
{children}
+
+ ); +}; + +DockIcon.displayName = "DockIcon"; + +export { Dock, DockIcon, dockVariants }; diff --git a/components/ui/dotted-map.tsx b/components/ui/dotted-map.tsx new file mode 100644 index 0000000..ecf8642 --- /dev/null +++ b/components/ui/dotted-map.tsx @@ -0,0 +1,103 @@ +import * as React from "react"; +import { createMap } from "svg-dotted-map"; + +import { cn } from "@/lib/utils"; + +interface Marker { + lat: number; + lng: number; + size?: number; +} + +export interface DottedMapProps extends React.SVGProps { + width?: number; + height?: number; + mapSamples?: number; + markers?: Marker[]; + dotColor?: string; + markerColor?: string; + dotRadius?: number; + stagger?: boolean; +} + +export function DottedMap({ + width = 150, + height = 75, + mapSamples = 5000, + markers = [], + markerColor = "#FF6900", + dotRadius = 0.2, + stagger = true, + className, + style, +}: DottedMapProps) { + const { points, addMarkers } = createMap({ + width, + height, + mapSamples, + }); + + const processedMarkers = addMarkers(markers); + + // Compute stagger helpers in a single, simple pass + const { xStep, yToRowIndex } = React.useMemo(() => { + const sorted = [...points].sort((a, b) => a.y - b.y || a.x - b.x); + const rowMap = new Map(); + let step = 0; + let prevY = Number.NaN; + let prevXInRow = Number.NaN; + + for (const p of sorted) { + if (p.y !== prevY) { + // new row + prevY = p.y; + prevXInRow = Number.NaN; + if (!rowMap.has(p.y)) rowMap.set(p.y, rowMap.size); + } + if (!Number.isNaN(prevXInRow)) { + const delta = p.x - prevXInRow; + if (delta > 0) step = step === 0 ? delta : Math.min(step, delta); + } + prevXInRow = p.x; + } + + return { xStep: step || 1, yToRowIndex: rowMap }; + }, [points]); + + return ( + + {points.map((point, index) => { + const rowIndex = yToRowIndex.get(point.y) ?? 0; + const offsetX = stagger && rowIndex % 2 === 1 ? xStep / 2 : 0; + return ( + + ); + })} + {processedMarkers.map((marker, index) => { + const rowIndex = yToRowIndex.get(marker.y) ?? 0; + const offsetX = stagger && rowIndex % 2 === 1 ? xStep / 2 : 0; + return ( + + ); + })} + + ); +} diff --git a/components/ui/progressive-blur.tsx b/components/ui/progressive-blur.tsx new file mode 100644 index 0000000..65bf888 --- /dev/null +++ b/components/ui/progressive-blur.tsx @@ -0,0 +1,116 @@ +"use client"; + +import type React from "react"; + +import { cn } from "@/lib/utils"; + +export interface ProgressiveBlurProps { + className?: string; + height?: string; + position?: "top" | "bottom" | "both"; + blurLevels?: number[]; + children?: React.ReactNode; +} + +export function ProgressiveBlur({ + className, + height = "30%", + position = "bottom", + blurLevels = [0.5, 1, 2, 4, 8, 16, 32, 64], +}: ProgressiveBlurProps) { + // Create array with length equal to blurLevels.length - 2 (for before/after pseudo elements) + const divElements = Array(blurLevels.length - 2).fill(null); + + return ( +
+ {/* First blur layer (pseudo element) */} +
+ + {/* Middle blur layers */} + {divElements.map((_, index) => { + const blurIndex = index + 1; + const startPercent = blurIndex * 12.5; + const midPercent = (blurIndex + 1) * 12.5; + const endPercent = (blurIndex + 2) * 12.5; + + const maskGradient = + position === "bottom" + ? `linear-gradient(to bottom, rgba(0,0,0,0) ${startPercent}%, rgba(0,0,0,1) ${midPercent}%, rgba(0,0,0,1) ${endPercent}%, rgba(0,0,0,0) ${endPercent + 12.5}%)` + : position === "top" + ? `linear-gradient(to top, rgba(0,0,0,0) ${startPercent}%, rgba(0,0,0,1) ${midPercent}%, rgba(0,0,0,1) ${endPercent}%, rgba(0,0,0,0) ${endPercent + 12.5}%)` + : `linear-gradient(rgba(0,0,0,0) 0%, rgba(0,0,0,1) 5%, rgba(0,0,0,1) 95%, rgba(0,0,0,0) 100%)`; + + return ( +
+ index + }`} + className="absolute inset-0" + style={{ + zIndex: index + 2, + backdropFilter: `blur(${blurLevels[blurIndex]}px)`, + WebkitBackdropFilter: `blur(${blurLevels[blurIndex]}px)`, + maskImage: maskGradient, + WebkitMaskImage: maskGradient, + }} + /> + ); + })} + + {/* Last blur layer (pseudo element) */} +
+
+ ); +} diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx new file mode 100644 index 0000000..ed700c2 --- /dev/null +++ b/components/ui/tooltip.tsx @@ -0,0 +1,61 @@ +"use client"; + +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ); +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/package.json b/package.json index 4f0aa01..571fd79 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,13 @@ "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", - "@tanstack/react-form": "^1.23.8", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-form": "^1.25.0", "@tanstack/react-pacer": "^0.16.4", - "@tanstack/react-query": "^5.90.5", + "@tanstack/react-query": "^5.90.10", "@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-virtual": "^3.13.12", "arctic": "^3.7.0", @@ -40,16 +41,19 @@ "drizzle-orm": "^0.44.7", "drizzle-seed": "^0.3.1", "lucide-react": "^0.548.0", + "motion": "^12.23.24", "nanoid": "^5.1.6", "next": "15.5.6", "next-themes": "^0.4.6", + "ogl": "^1.0.11", "react": "^19.2.0", "react-dom": "^19.2.0", "react-intersection-observer": "^9.16.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1", + "svg-dotted-map": "^2.0.1", + "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2", "zod": "^4.1.12" @@ -57,14 +61,14 @@ "devDependencies": { "@biomejs/biome": "2.3.1", "@faker-js/faker": "^10.1.0", - "@tailwindcss/postcss": "^4.1.16", + "@tailwindcss/postcss": "^4.1.17", "@tailwindcss/typography": "^0.5.19", - "@types/node": "^22.18.12", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", + "@types/node": "^22.19.1", + "@types/react": "^19.2.6", + "@types/react-dom": "^19.2.3", "drizzle-kit": "0.31.5", - "lefthook": "^2.0.1", - "tailwindcss": "^4.1.16", + "lefthook": "^2.0.4", + "tailwindcss": "^4.1.17", "typescript": "^5.9.3" } }