this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: build lively forms v0

+5132
+47
.gitignore
··· 1 + # dependencies 2 + /node_modules 3 + /.pnp 4 + .pnp.* 5 + .yarn/* 6 + !.yarn/patches 7 + !.yarn/plugins 8 + !.yarn/releases 9 + !.yarn/versions 10 + 11 + # testing 12 + /coverage 13 + 14 + # next.js 15 + /.next/ 16 + /out/ 17 + 18 + # production 19 + /build 20 + /dist 21 + 22 + # misc 23 + .DS_Store 24 + *.pem 25 + 26 + # debug 27 + npm-debug.log* 28 + yarn-debug.log* 29 + yarn-error.log* 30 + .pnpm-debug.log* 31 + 32 + # env files 33 + .env* 34 + 35 + # vercel 36 + .vercel 37 + 38 + # typescript 39 + *.tsbuildinfo 40 + next-env.d.ts 41 + 42 + # prisma 43 + /dev.db 44 + /dev.db-journal 45 + 46 + # podman volumes 47 + /.local-postgres-data
+139
README.md
··· 1 + # Lively Forms 2 + 3 + Lively Forms is a Typeform-inspired conversational form builder built with Next.js, Auth.js, Prisma, PostgreSQL, Tailwind, Framer Motion, and dnd-kit. 4 + 5 + ## Stack 6 + 7 + - Next.js App Router 8 + - TypeScript 9 + - Tailwind CSS 10 + - shadcn/ui-style local primitives 11 + - Framer Motion 12 + - dnd-kit 13 + - Auth.js (`next-auth`) with Google OAuth for creators 14 + - Prisma + PostgreSQL 15 + - Podman Compose for local Postgres 16 + 17 + ## Local setup 18 + 19 + ### 1. Install dependencies 20 + 21 + ```bash 22 + bun install 23 + ``` 24 + 25 + ### 2. Start Postgres with Podman 26 + 27 + ```bash 28 + bun run db:up 29 + ``` 30 + 31 + This uses `compose.yaml` and starts a local Postgres container on `localhost:5432`. 32 + 33 + ### 3. Create local env file 34 + 35 + ```bash 36 + cp .env.example .env 37 + ``` 38 + 39 + Fill in: 40 + - `AUTH_SECRET` 41 + - `AUTH_GOOGLE_ID` 42 + - `AUTH_GOOGLE_SECRET` 43 + - `NEXTAUTH_URL` (keep `http://localhost:3000` for local dev) 44 + 45 + Generate a secret, for example: 46 + 47 + ```bash 48 + openssl rand -base64 32 49 + ``` 50 + 51 + ### 4. Configure Google OAuth for local development 52 + 53 + Yes — Google OAuth can work locally, but you must create your own OAuth client in Google Cloud and allow localhost. 54 + 55 + In Google Cloud Console: 56 + 57 + 1. Open **APIs & Services → Credentials** 58 + 2. Create an **OAuth 2.0 Client ID** 59 + 3. Choose **Web application** 60 + 4. Add these local settings: 61 + 62 + **Authorized JavaScript origins** 63 + - `http://localhost:3000` 64 + 65 + **Authorized redirect URIs** 66 + - `http://localhost:3000/api/auth/callback/google` 67 + 68 + Then copy the generated values into `.env`: 69 + 70 + ```env 71 + AUTH_GOOGLE_ID="your-client-id" 72 + AUTH_GOOGLE_SECRET="your-client-secret" 73 + ``` 74 + 75 + If these values are missing or wrong, Google will show: 76 + - `Error 401: invalid_client` 77 + - `The OAuth client was not found` 78 + 79 + ### 5. Run Prisma migration 80 + 81 + ```bash 82 + bun run prisma:migrate 83 + ``` 84 + 85 + ### 6. Start the app 86 + 87 + ```bash 88 + bun run dev 89 + ``` 90 + 91 + Open `http://localhost:3000`. 92 + 93 + ## Useful commands 94 + 95 + ```bash 96 + bun run dev 97 + bun run build 98 + bun run lint 99 + bun run db:up 100 + bun run db:down 101 + bun run db:logs 102 + bun run prisma:generate 103 + bun run prisma:migrate 104 + bun run prisma:studio 105 + ``` 106 + 107 + ## Product surfaces 108 + 109 + ### Creator app 110 + - `/login` 111 + - `/dashboard` 112 + - `/forms/[id]/edit` 113 + - `/forms/[id]/responses` 114 + - `/forms/[id]/responses/[responseId]` 115 + 116 + ### Public app 117 + - `/f/[slug]` 118 + 119 + ## Current v0 scope 120 + 121 + - Creator Google sign-in 122 + - Owned form dashboard 123 + - Master-detail builder 124 + - Block types: 125 + - text 126 + - short text 127 + - long text 128 + - single choice 129 + - multiple choice 130 + - Publish / unpublish 131 + - Anonymous public submissions 132 + - Responses list and detail views 133 + 134 + ## Notes 135 + 136 + - Creator auth is used only for creating, editing, publishing, and reviewing forms. 137 + - Public respondents do not log in. 138 + - Responses are stored anonymously. 139 + - Editing a published form updates the live public form immediately in v0.
+17
app/(creator)/actions.ts
··· 1 + "use server"; 2 + 3 + import { redirect } from "next/navigation"; 4 + 5 + import { getServerAuthSession } from "@/lib/auth"; 6 + import { createDraftForm } from "@/lib/forms"; 7 + 8 + export async function createFormAction() { 9 + const session = await getServerAuthSession(); 10 + 11 + if (!session?.user?.id) { 12 + redirect("/login"); 13 + } 14 + 15 + const form = await createDraftForm(session.user.id); 16 + redirect(`/forms/${form.id}/edit`); 17 + }
+5
app/(creator)/dashboard/loading.tsx
··· 1 + import { LoadingShell } from "@/components/loading-shell"; 2 + 3 + export default function DashboardLoading() { 4 + return <LoadingShell title="dashboard" />; 5 + }
+103
app/(creator)/dashboard/page.tsx
··· 1 + import Link from "next/link"; 2 + import { ArrowRight, Plus } from "lucide-react"; 3 + 4 + import { EmptyState } from "@/components/empty-state"; 5 + import { Badge } from "@/components/ui/badge"; 6 + import { Button } from "@/components/ui/button"; 7 + import { Card } from "@/components/ui/card"; 8 + import { getServerAuthSession } from "@/lib/auth"; 9 + import { listFormsForOwner } from "@/lib/forms"; 10 + import { formatDate } from "@/lib/utils"; 11 + import { createFormAction } from "@/app/(creator)/actions"; 12 + 13 + export default async function DashboardPage() { 14 + const session = await getServerAuthSession(); 15 + const forms = await listFormsForOwner(session!.user.id); 16 + 17 + return ( 18 + <div className="space-y-8"> 19 + <section className="flex flex-col gap-4 rounded-[32px] border border-black/8 bg-[linear-gradient(180deg,rgba(255,255,255,0.95),rgba(235,247,235,0.92))] px-8 py-8 shadow-[0_24px_70px_rgba(15,23,42,0.06)] lg:flex-row lg:items-end lg:justify-between"> 20 + <div className="space-y-3"> 21 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Creator dashboard</p> 22 + <h1 className="font-display text-5xl leading-tight text-[var(--ink)]">Build linear forms that feel composed.</h1> 23 + <p className="max-w-2xl text-sm leading-7 text-[var(--muted)]"> 24 + Start drafts fast, keep editing focused, and send people into a one-screen-at-a-time runner that stays 25 + anonymous on submission. 26 + </p> 27 + </div> 28 + <form action={createFormAction}> 29 + <Button size="lg" type="submit"> 30 + <Plus className="size-4" /> 31 + New form 32 + </Button> 33 + </form> 34 + </section> 35 + 36 + {forms.length === 0 ? ( 37 + <EmptyState 38 + eyebrow="No forms yet" 39 + title="Start with a draft and shape the conversation." 40 + description="Every new form begins with a welcome text block and a first question so you can immediately refine the flow." 41 + action={ 42 + <form action={createFormAction}> 43 + <Button type="submit"> 44 + <Plus className="size-4" /> 45 + Create your first form 46 + </Button> 47 + </form> 48 + } 49 + /> 50 + ) : ( 51 + <section className="grid gap-5 lg:grid-cols-2"> 52 + {forms.map((form) => ( 53 + <Card key={form.id} className="p-6"> 54 + <div className="flex items-start justify-between gap-4"> 55 + <div> 56 + <Badge className={form.status === "PUBLISHED" ? "bg-[var(--accent-soft)] text-[#2f5d35]" : "bg-black/5"}> 57 + {form.status === "PUBLISHED" ? "Published" : "Draft"} 58 + </Badge> 59 + <h2 className="mt-4 font-display text-3xl text-[var(--ink)]">{form.title}</h2> 60 + <p className="mt-3 max-w-xl text-sm leading-7 text-[var(--muted)]"> 61 + {form.description || "No description yet — open the builder to shape the story of the form."} 62 + </p> 63 + </div> 64 + <Link href={`/forms/${form.id}/edit`}> 65 + <Button variant="secondary" size="sm"> 66 + Edit 67 + </Button> 68 + </Link> 69 + </div> 70 + 71 + <div className="mt-6 grid gap-3 text-sm text-[var(--muted)] sm:grid-cols-3"> 72 + <div> 73 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Share URL</p> 74 + <p className="mt-2 truncate">/{form.slug}</p> 75 + </div> 76 + <div> 77 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Responses</p> 78 + <p className="mt-2">{form.responseCount}</p> 79 + </div> 80 + <div> 81 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Updated</p> 82 + <p className="mt-2">{formatDate(form.updatedAt)}</p> 83 + </div> 84 + </div> 85 + 86 + <div className="mt-6 flex flex-wrap gap-3"> 87 + <Link href={`/forms/${form.id}/edit`}> 88 + <Button> 89 + Open builder 90 + <ArrowRight className="size-4" /> 91 + </Button> 92 + </Link> 93 + <Link href={`/forms/${form.id}/responses`}> 94 + <Button variant="secondary">View responses</Button> 95 + </Link> 96 + </div> 97 + </Card> 98 + ))} 99 + </section> 100 + )} 101 + </div> 102 + ); 103 + }
+5
app/(creator)/forms/[id]/edit/loading.tsx
··· 1 + import { LoadingShell } from "@/components/loading-shell"; 2 + 3 + export default function EditFormLoading() { 4 + return <LoadingShell title="form builder" />; 5 + }
+25
app/(creator)/forms/[id]/edit/page.tsx
··· 1 + import { notFound } from "next/navigation"; 2 + 3 + import { FormBuilder } from "@/components/form-builder"; 4 + import { getServerAuthSession } from "@/lib/auth"; 5 + import { AppError } from "@/lib/errors"; 6 + import { getOwnedFormForBuilder } from "@/lib/forms"; 7 + 8 + export default async function EditFormPage({ params }: { params: Promise<{ id: string }> }) { 9 + const session = await getServerAuthSession(); 10 + const { id } = await params; 11 + 12 + let form; 13 + 14 + try { 15 + form = await getOwnedFormForBuilder(session!.user.id, id); 16 + } catch (error) { 17 + if (error instanceof AppError && error.status === 404) { 18 + notFound(); 19 + } 20 + 21 + throw error; 22 + } 23 + 24 + return <FormBuilder initialForm={form} />; 25 + }
+89
app/(creator)/forms/[id]/responses/[responseId]/page.tsx
··· 1 + import Link from "next/link"; 2 + import { notFound } from "next/navigation"; 3 + 4 + import { Badge } from "@/components/ui/badge"; 5 + import { Button } from "@/components/ui/button"; 6 + import { Card } from "@/components/ui/card"; 7 + import { getServerAuthSession } from "@/lib/auth"; 8 + import { AppError } from "@/lib/errors"; 9 + import { getOwnedResponseDetail } from "@/lib/forms"; 10 + import { formatDate } from "@/lib/utils"; 11 + 12 + export default async function ResponseDetailPage({ 13 + params, 14 + }: { 15 + params: Promise<{ id: string; responseId: string }>; 16 + }) { 17 + const session = await getServerAuthSession(); 18 + const { id, responseId } = await params; 19 + 20 + let response; 21 + 22 + try { 23 + response = await getOwnedResponseDetail(session!.user.id, id, responseId); 24 + } catch (error) { 25 + if (error instanceof AppError && error.status === 404) { 26 + notFound(); 27 + } 28 + 29 + throw error; 30 + } 31 + 32 + return ( 33 + <div className="space-y-6"> 34 + <section className="flex flex-col gap-4 rounded-[30px] border border-black/8 bg-white/70 px-6 py-5 backdrop-blur-sm lg:flex-row lg:items-end lg:justify-between"> 35 + <div> 36 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Submission replay</p> 37 + <h1 className="mt-3 font-display text-4xl text-[var(--ink)]">{formatDate(response.submittedAt)}</h1> 38 + <p className="mt-2 text-sm leading-7 text-[var(--muted)]">Text blocks appear as context markers while answerable steps show the submitted value.</p> 39 + </div> 40 + <Link href={`/forms/${id}/responses`}> 41 + <Button variant="secondary">Back to responses</Button> 42 + </Link> 43 + </section> 44 + 45 + <div className="space-y-4"> 46 + {response.blocks.map((block) => { 47 + const answer = response.answers[block.id]; 48 + 49 + if (block.type === "TEXT") { 50 + return ( 51 + <Card key={block.id} className="border-dashed bg-[var(--bg-strong)] p-6"> 52 + <Badge>Context block</Badge> 53 + <h2 className="mt-4 font-display text-3xl text-[var(--ink)]">{block.title || "Context"}</h2> 54 + {block.description ? <p className="mt-3 text-sm leading-7 text-[var(--muted)]">{block.description}</p> : null} 55 + {"body" in block.config ? <p className="mt-4 text-base leading-8 text-[var(--ink)]/85">{block.config.body}</p> : null} 56 + </Card> 57 + ); 58 + } 59 + 60 + return ( 61 + <Card key={block.id} className="p-6"> 62 + <div className="flex flex-wrap items-center gap-3"> 63 + <Badge>{block.type.replaceAll("_", " ")}</Badge> 64 + {block.required ? <Badge className="bg-amber-100 text-amber-700">Required</Badge> : null} 65 + </div> 66 + <h2 className="mt-4 font-display text-3xl text-[var(--ink)]">{block.title}</h2> 67 + {block.description ? <p className="mt-3 text-sm leading-7 text-[var(--muted)]">{block.description}</p> : null} 68 + <div className="mt-6 rounded-[24px] border border-black/8 bg-[var(--bg-strong)] px-5 py-4 text-[var(--ink)]"> 69 + {Array.isArray(answer) ? ( 70 + <div className="flex flex-wrap gap-2"> 71 + {answer.map((value) => ( 72 + <Badge key={value} className="bg-white text-[var(--ink)]"> 73 + {value} 74 + </Badge> 75 + ))} 76 + </div> 77 + ) : answer ? ( 78 + <p className="whitespace-pre-wrap text-base leading-8">{answer}</p> 79 + ) : ( 80 + <p className="text-sm text-[var(--muted)]">No answer provided.</p> 81 + )} 82 + </div> 83 + </Card> 84 + ); 85 + })} 86 + </div> 87 + </div> 88 + ); 89 + }
+5
app/(creator)/forms/[id]/responses/loading.tsx
··· 1 + import { LoadingShell } from "@/components/loading-shell"; 2 + 3 + export default function ResponsesLoading() { 4 + return <LoadingShell title="responses" />; 5 + }
+83
app/(creator)/forms/[id]/responses/page.tsx
··· 1 + import Link from "next/link"; 2 + import { ArrowRight } from "lucide-react"; 3 + import { notFound } from "next/navigation"; 4 + 5 + import { EmptyState } from "@/components/empty-state"; 6 + import { Badge } from "@/components/ui/badge"; 7 + import { Button } from "@/components/ui/button"; 8 + import { Card } from "@/components/ui/card"; 9 + import { getServerAuthSession } from "@/lib/auth"; 10 + import { AppError } from "@/lib/errors"; 11 + import { listResponsesForOwnedForm } from "@/lib/forms"; 12 + import { formatDate } from "@/lib/utils"; 13 + 14 + export default async function ResponsesPage({ params }: { params: Promise<{ id: string }> }) { 15 + const session = await getServerAuthSession(); 16 + const { id } = await params; 17 + 18 + let form; 19 + let responses; 20 + 21 + try { 22 + const payload = await listResponsesForOwnedForm(session!.user.id, id); 23 + form = payload.form; 24 + responses = payload.responses; 25 + } catch (error) { 26 + if (error instanceof AppError && error.status === 404) { 27 + notFound(); 28 + } 29 + 30 + throw error; 31 + } 32 + 33 + return ( 34 + <div className="space-y-6"> 35 + <section className="flex flex-col gap-4 rounded-[30px] border border-black/8 bg-white/70 px-6 py-5 backdrop-blur-sm lg:flex-row lg:items-end lg:justify-between"> 36 + <div> 37 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Responses</p> 38 + <h1 className="mt-3 font-display text-4xl text-[var(--ink)]">{form.title}</h1> 39 + <p className="mt-2 text-sm leading-7 text-[var(--muted)]">Read each anonymous submission in form order and keep the conversational context intact.</p> 40 + </div> 41 + <div className="flex gap-3"> 42 + <Badge>{responses.length} submissions</Badge> 43 + <Link href={`/forms/${form.id}/edit`}> 44 + <Button variant="secondary">Back to builder</Button> 45 + </Link> 46 + </div> 47 + </section> 48 + 49 + {responses.length === 0 ? ( 50 + <EmptyState 51 + eyebrow="No submissions yet" 52 + title="Publish the form to start collecting responses." 53 + description="Only published forms are available on the public route, and every respondent stays anonymous on submission." 54 + action={ 55 + <Link href={`/forms/${form.id}/edit`}> 56 + <Button>Open builder</Button> 57 + </Link> 58 + } 59 + /> 60 + ) : ( 61 + <div className="grid gap-4"> 62 + {responses.map((response) => ( 63 + <Card key={response.id} className="p-6"> 64 + <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> 65 + <div> 66 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Anonymous response</p> 67 + <h2 className="mt-3 font-display text-3xl text-[var(--ink)]">{formatDate(response.submittedAt)}</h2> 68 + <p className="mt-2 text-sm text-[var(--muted)]">{response.answerCount} answers captured</p> 69 + </div> 70 + <Link href={`/forms/${form.id}/responses/${response.id}`}> 71 + <Button> 72 + Open response 73 + <ArrowRight className="size-4" /> 74 + </Button> 75 + </Link> 76 + </div> 77 + </Card> 78 + ))} 79 + </div> 80 + )} 81 + </div> 82 + ); 83 + }
+15
app/(creator)/forms/new/route.ts
··· 1 + import { redirect } from "next/navigation"; 2 + 3 + import { getServerAuthSession } from "@/lib/auth"; 4 + import { createDraftForm } from "@/lib/forms"; 5 + 6 + export async function GET() { 7 + const session = await getServerAuthSession(); 8 + 9 + if (!session?.user?.id) { 10 + redirect("/login"); 11 + } 12 + 13 + const form = await createDraftForm(session.user.id); 14 + redirect(`/forms/${form.id}/edit`); 15 + }
+38
app/(creator)/layout.tsx
··· 1 + import Link from "next/link"; 2 + import { redirect } from "next/navigation"; 3 + 4 + import { SignOutButton } from "@/components/auth/sign-out-button"; 5 + import { getServerAuthSession } from "@/lib/auth"; 6 + 7 + export default async function CreatorLayout({ children }: { children: React.ReactNode }) { 8 + const session = await getServerAuthSession(); 9 + 10 + if (!session?.user?.id) { 11 + redirect("/login"); 12 + } 13 + 14 + return ( 15 + <main className="mx-auto min-h-screen w-full max-w-7xl px-6 py-8 lg:px-10"> 16 + <header className="mb-8 flex flex-col gap-4 rounded-[30px] border border-black/8 bg-white/70 px-6 py-5 backdrop-blur-sm lg:flex-row lg:items-center lg:justify-between"> 17 + <div className="flex items-center gap-4"> 18 + <Link href="/dashboard" className="space-y-1"> 19 + <p className="text-xs font-semibold uppercase tracking-[0.32em] text-[var(--accent)]">Lively Forms</p> 20 + <p className="text-sm text-[var(--muted)]">Creator workspace</p> 21 + </Link> 22 + <div className="hidden h-12 w-px bg-black/8 lg:block" /> 23 + <div className="hidden lg:block"> 24 + <p className="font-display text-2xl text-[var(--ink)]">{session.user.name ?? session.user.email ?? "Creator"}</p> 25 + <p className="text-sm text-[var(--muted)]">Own forms, publish calmly, review responses.</p> 26 + </div> 27 + </div> 28 + <div className="flex items-center gap-3"> 29 + <Link href="/dashboard" className="text-sm font-medium text-[var(--muted)] transition hover:text-[var(--ink)]"> 30 + Dashboard 31 + </Link> 32 + <SignOutButton /> 33 + </div> 34 + </header> 35 + {children} 36 + </main> 37 + ); 38 + }
+7
app/api/auth/[...nextauth]/route.ts
··· 1 + import NextAuth from "next-auth"; 2 + 3 + import { authOptions } from "@/lib/auth"; 4 + 5 + const handler = NextAuth(authOptions); 6 + 7 + export { handler as GET, handler as POST };
+40
app/api/forms/[formId]/blocks/[blockId]/route.ts
··· 1 + import { NextResponse } from "next/server"; 2 + 3 + import { handleRouteError } from "@/lib/api"; 4 + import { getServerAuthSession } from "@/lib/auth"; 5 + import { deleteOwnedBlock, updateOwnedBlock } from "@/lib/forms"; 6 + 7 + export async function PATCH(request: Request, context: { params: Promise<{ formId: string; blockId: string }> }) { 8 + const session = await getServerAuthSession(); 9 + 10 + if (!session?.user?.id) { 11 + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 12 + } 13 + 14 + try { 15 + const payload = await request.json(); 16 + const { formId, blockId } = await context.params; 17 + const block = await updateOwnedBlock(session.user.id, formId, blockId, payload); 18 + 19 + return NextResponse.json({ block }); 20 + } catch (error) { 21 + return handleRouteError(error); 22 + } 23 + } 24 + 25 + export async function DELETE(_: Request, context: { params: Promise<{ formId: string; blockId: string }> }) { 26 + const session = await getServerAuthSession(); 27 + 28 + if (!session?.user?.id) { 29 + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 30 + } 31 + 32 + try { 33 + const { formId, blockId } = await context.params; 34 + await deleteOwnedBlock(session.user.id, formId, blockId); 35 + 36 + return NextResponse.json({ ok: true }); 37 + } catch (error) { 38 + return handleRouteError(error); 39 + } 40 + }
+23
app/api/forms/[formId]/blocks/reorder/route.ts
··· 1 + import { NextResponse } from "next/server"; 2 + 3 + import { handleRouteError } from "@/lib/api"; 4 + import { getServerAuthSession } from "@/lib/auth"; 5 + import { reorderOwnedBlocks } from "@/lib/forms"; 6 + 7 + export async function POST(request: Request, context: { params: Promise<{ formId: string }> }) { 8 + const session = await getServerAuthSession(); 9 + 10 + if (!session?.user?.id) { 11 + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 12 + } 13 + 14 + try { 15 + const payload = await request.json(); 16 + const { formId } = await context.params; 17 + const form = await reorderOwnedBlocks(session.user.id, formId, payload); 18 + 19 + return NextResponse.json({ form }); 20 + } catch (error) { 21 + return handleRouteError(error); 22 + } 23 + }
+23
app/api/forms/[formId]/blocks/route.ts
··· 1 + import { NextResponse } from "next/server"; 2 + 3 + import { handleRouteError } from "@/lib/api"; 4 + import { getServerAuthSession } from "@/lib/auth"; 5 + import { addOwnedBlock } from "@/lib/forms"; 6 + 7 + export async function POST(request: Request, context: { params: Promise<{ formId: string }> }) { 8 + const session = await getServerAuthSession(); 9 + 10 + if (!session?.user?.id) { 11 + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 12 + } 13 + 14 + try { 15 + const payload = await request.json(); 16 + const { formId } = await context.params; 17 + const block = await addOwnedBlock(session.user.id, formId, payload); 18 + 19 + return NextResponse.json({ block }); 20 + } catch (error) { 21 + return handleRouteError(error); 22 + } 23 + }
+23
app/api/forms/[formId]/publish/route.ts
··· 1 + import { NextResponse } from "next/server"; 2 + 3 + import { handleRouteError } from "@/lib/api"; 4 + import { getServerAuthSession } from "@/lib/auth"; 5 + import { setOwnedFormPublished } from "@/lib/forms"; 6 + 7 + export async function POST(request: Request, context: { params: Promise<{ formId: string }> }) { 8 + const session = await getServerAuthSession(); 9 + 10 + if (!session?.user?.id) { 11 + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 12 + } 13 + 14 + try { 15 + const payload = await request.json(); 16 + const { formId } = await context.params; 17 + const form = await setOwnedFormPublished(session.user.id, formId, payload); 18 + 19 + return NextResponse.json({ form }); 20 + } catch (error) { 21 + return handleRouteError(error); 22 + } 23 + }
+23
app/api/forms/[formId]/route.ts
··· 1 + import { NextResponse } from "next/server"; 2 + 3 + import { handleRouteError } from "@/lib/api"; 4 + import { getServerAuthSession } from "@/lib/auth"; 5 + import { updateOwnedFormMetadata } from "@/lib/forms"; 6 + 7 + export async function PATCH(request: Request, context: { params: Promise<{ formId: string }> }) { 8 + const session = await getServerAuthSession(); 9 + 10 + if (!session?.user?.id) { 11 + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); 12 + } 13 + 14 + try { 15 + const payload = await request.json(); 16 + const { formId } = await context.params; 17 + const form = await updateOwnedFormMetadata(session.user.id, formId, payload); 18 + 19 + return NextResponse.json({ form }); 20 + } catch (error) { 21 + return handleRouteError(error); 22 + } 23 + }
+16
app/api/public/forms/[slug]/responses/route.ts
··· 1 + import { NextResponse } from "next/server"; 2 + 3 + import { handleRouteError } from "@/lib/api"; 4 + import { createAnonymousResponse } from "@/lib/forms"; 5 + 6 + export async function POST(request: Request, context: { params: Promise<{ slug: string }> }) { 7 + try { 8 + const payload = await request.json(); 9 + const { slug } = await context.params; 10 + const response = await createAnonymousResponse(slug, payload.answers ?? {}); 11 + 12 + return NextResponse.json({ responseId: response.id }); 13 + } catch (error) { 14 + return handleRouteError(error); 15 + } 16 + }
+32
app/error.tsx
··· 1 + "use client"; 2 + 3 + import { useEffect } from "react"; 4 + 5 + import { Button } from "@/components/ui/button"; 6 + import { Card } from "@/components/ui/card"; 7 + 8 + export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { 9 + useEffect(() => { 10 + console.error(error); 11 + }, [error]); 12 + 13 + return ( 14 + <html lang="en"> 15 + <body> 16 + <main className="mx-auto flex min-h-screen max-w-3xl items-center px-6 py-16"> 17 + <Card className="w-full px-8 py-10 text-center"> 18 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Something broke</p> 19 + <h1 className="mt-4 font-display text-4xl text-[var(--ink)]">The forms hit an unexpected error.</h1> 20 + <p className="mx-auto mt-3 max-w-lg text-sm leading-7 text-[var(--muted)]"> 21 + Try the action again. If it keeps happening, check your environment variables, Google OAuth setup, and 22 + local Postgres container. 23 + </p> 24 + <div className="mt-6 flex justify-center"> 25 + <Button onClick={() => reset()}>Try again</Button> 26 + </div> 27 + </Card> 28 + </main> 29 + </body> 30 + </html> 31 + ); 32 + }
+5
app/f/[slug]/loading.tsx
··· 1 + import { LoadingShell } from "@/components/loading-shell"; 2 + 3 + export default function PublicFormLoading() { 4 + return <LoadingShell title="public form" />; 5 + }
+19
app/f/[slug]/page.tsx
··· 1 + import { notFound } from "next/navigation"; 2 + 3 + import { PublicFormRunner } from "@/components/public-form-runner"; 4 + import { getPublicFormBySlug } from "@/lib/forms"; 5 + 6 + export default async function PublicFormPage({ params }: { params: Promise<{ slug: string }> }) { 7 + const { slug } = await params; 8 + const form = await getPublicFormBySlug(slug); 9 + 10 + if (!form) { 11 + notFound(); 12 + } 13 + 14 + return ( 15 + <main className="mx-auto flex min-h-screen max-w-6xl items-center justify-center px-4 py-10 sm:px-6 lg:px-10"> 16 + <PublicFormRunner form={form} /> 17 + </main> 18 + ); 19 + }
app/favicon.ico

This is a binary file and will not be displayed.

+59
app/globals.css
··· 1 + @import "tailwindcss"; 2 + 3 + :root { 4 + --bg: #f3f8f1; 5 + --bg-strong: #e3f0e0; 6 + --surface: rgba(255, 255, 255, 0.72); 7 + --surface-strong: rgba(255, 255, 255, 0.92); 8 + --ink: #162015; 9 + --muted: #5f7060; 10 + --accent: #74ad7c; 11 + --accent-soft: #b9dfb9; 12 + --line: rgba(22, 32, 21, 0.08); 13 + } 14 + 15 + @theme inline { 16 + --color-background: var(--bg); 17 + --color-foreground: var(--ink); 18 + --font-sans: var(--font-manrope); 19 + --font-display: var(--font-fraunces); 20 + } 21 + 22 + * { 23 + box-sizing: border-box; 24 + } 25 + 26 + body { 27 + min-height: 100vh; 28 + background: 29 + radial-gradient(circle at top, rgba(255, 255, 255, 0.82), transparent 42%), 30 + linear-gradient(180deg, #fbfef9 0%, #f3f8f1 48%, #e7f2e5 100%); 31 + color: var(--ink); 32 + font-family: var(--font-manrope), sans-serif; 33 + } 34 + 35 + body::before { 36 + position: fixed; 37 + inset: 0; 38 + pointer-events: none; 39 + content: ""; 40 + background-image: linear-gradient(rgba(255,255,255,0.16) 1px, transparent 1px), 41 + linear-gradient(90deg, rgba(255,255,255,0.16) 1px, transparent 1px); 42 + background-size: 32px 32px; 43 + mask-image: radial-gradient(circle at center, black, transparent 82%); 44 + opacity: 0.5; 45 + } 46 + 47 + main { 48 + position: relative; 49 + z-index: 1; 50 + } 51 + 52 + a { 53 + color: inherit; 54 + text-decoration: none; 55 + } 56 + 57 + ::selection { 58 + background: rgba(116, 173, 124, 0.18); 59 + }
+30
app/layout.tsx
··· 1 + import type { Metadata } from "next"; 2 + import { Fraunces, Manrope } from "next/font/google"; 3 + import "./globals.css"; 4 + 5 + const fraunces = Fraunces({ 6 + variable: "--font-fraunces", 7 + subsets: ["latin"], 8 + }); 9 + 10 + const manrope = Manrope({ 11 + variable: "--font-manrope", 12 + subsets: ["latin"], 13 + }); 14 + 15 + export const metadata: Metadata = { 16 + title: "Lively Forms", 17 + description: "Lively Forms is a conversational form builder with an elegant creator workflow.", 18 + }; 19 + 20 + export default function RootLayout({ 21 + children, 22 + }: Readonly<{ 23 + children: React.ReactNode; 24 + }>) { 25 + return ( 26 + <html lang="en"> 27 + <body className={`${fraunces.variable} ${manrope.variable}`}>{children}</body> 28 + </html> 29 + ); 30 + }
+40
app/login/page.tsx
··· 1 + import { redirect } from "next/navigation"; 2 + 3 + import { GoogleSignInButton } from "@/components/auth/google-sign-in-button"; 4 + import { Card } from "@/components/ui/card"; 5 + import { getServerAuthSession } from "@/lib/auth"; 6 + 7 + export default async function LoginPage() { 8 + const session = await getServerAuthSession(); 9 + 10 + if (session?.user?.id) { 11 + redirect("/dashboard"); 12 + } 13 + 14 + return ( 15 + <main className="mx-auto flex min-h-screen max-w-4xl items-center px-6 py-16"> 16 + <Card className="grid w-full overflow-hidden lg:grid-cols-[1.1fr_0.9fr]"> 17 + <div className="bg-[#191611] p-10 text-white lg:p-14"> 18 + <p className="text-xs font-semibold uppercase tracking-[0.3em] text-[#b9dfb9]">Lively Forms creator access</p> 19 + <h1 className="mt-8 font-display text-5xl leading-tight">Sign in to craft the form experience.</h1> 20 + <p className="mt-6 max-w-md text-sm leading-7 text-white/70"> 21 + Creator auth is used only for owning, editing, publishing, and reviewing forms. Public respondents never 22 + log in and their submissions stay anonymous. 23 + </p> 24 + </div> 25 + <div className="flex items-center justify-center bg-[linear-gradient(180deg,rgba(255,255,255,0.92),rgba(237,247,237,0.96))] p-10 lg:p-14"> 26 + <div className="w-full max-w-sm space-y-6"> 27 + <div> 28 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Lively Forms login</p> 29 + <h2 className="mt-3 font-display text-4xl text-[var(--ink)]">Use Google to continue.</h2> 30 + </div> 31 + <p className="text-sm leading-7 text-[var(--muted)]"> 32 + Keep auth simple in v0 and stay focused on the form builder and conversational runner. 33 + </p> 34 + <GoogleSignInButton /> 35 + </div> 36 + </div> 37 + </Card> 38 + </main> 39 + ); 40 + }
+24
app/not-found.tsx
··· 1 + import Link from "next/link"; 2 + 3 + import { Button } from "@/components/ui/button"; 4 + import { Card } from "@/components/ui/card"; 5 + 6 + export default function NotFound() { 7 + return ( 8 + <main className="mx-auto flex min-h-screen max-w-3xl items-center px-6 py-16"> 9 + <Card className="w-full px-8 py-10 text-center"> 10 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Not found</p> 11 + <h1 className="mt-4 font-display text-4xl text-[var(--ink)]">That page or form doesn’t exist.</h1> 12 + <p className="mx-auto mt-3 max-w-lg text-sm leading-7 text-[var(--muted)]"> 13 + If you were expecting a public form, it may still be in draft. If you were editing a creator form, you may 14 + not have access to it. 15 + </p> 16 + <div className="mt-6 flex justify-center"> 17 + <Link href="/"> 18 + <Button>Back home</Button> 19 + </Link> 20 + </div> 21 + </Card> 22 + </main> 23 + ); 24 + }
+96
app/page.tsx
··· 1 + import Link from "next/link"; 2 + import { ArrowRight, Sparkles } from "lucide-react"; 3 + 4 + import { Button } from "@/components/ui/button"; 5 + import { Card } from "@/components/ui/card"; 6 + import { getServerAuthSession } from "@/lib/auth"; 7 + 8 + export default async function HomePage() { 9 + const session = await getServerAuthSession(); 10 + 11 + return ( 12 + <main className="mx-auto flex min-h-screen w-full max-w-6xl flex-col px-6 py-10 lg:px-10"> 13 + <header className="flex items-center justify-between rounded-full border border-black/8 bg-white/60 px-5 py-3 backdrop-blur-sm"> 14 + <div> 15 + <p className="text-xs font-semibold uppercase tracking-[0.32em] text-[var(--accent)]">Lively Forms</p> 16 + <p className="text-xs text-[var(--muted)]">Conversational forms with a softer, brighter rhythm.</p> 17 + </div> 18 + <Link href={session?.user ? "/dashboard" : "/login"}> 19 + <Button variant="secondary" size="sm"> 20 + {session?.user ? "Open dashboard" : "Creator login"} 21 + </Button> 22 + </Link> 23 + </header> 24 + 25 + <section className="grid flex-1 items-center gap-8 py-12 lg:grid-cols-[1.25fr_0.9fr] lg:py-20"> 26 + <div className="space-y-8"> 27 + <div className="inline-flex items-center gap-2 rounded-full border border-black/8 bg-white/70 px-4 py-2 text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)] backdrop-blur-sm"> 28 + <Sparkles className="size-3.5" /> 29 + Lively Forms v0 30 + </div> 31 + <div className="space-y-5"> 32 + <h1 className="max-w-4xl font-display text-6xl leading-none text-[var(--ink)] md:text-7xl"> 33 + Build forms that feel like a calm conversation. 34 + </h1> 35 + <p className="max-w-2xl text-lg leading-8 text-[var(--muted)]"> 36 + Create linear, elegant flows with focused screens, anonymous public submissions, and a polished 37 + creator workflow made for shipping quickly. 38 + </p> 39 + </div> 40 + <div className="flex flex-col gap-3 sm:flex-row"> 41 + <Link href={session?.user ? "/dashboard" : "/login"}> 42 + <Button size="lg"> 43 + {session?.user ? "Go to dashboard" : "Start with Google"} 44 + <ArrowRight className="size-4" /> 45 + </Button> 46 + </Link> 47 + <a href="#surfaces"> 48 + <Button variant="secondary" size="lg"> 49 + See product surfaces 50 + </Button> 51 + </a> 52 + </div> 53 + </div> 54 + 55 + <Card className="overflow-hidden bg-[linear-gradient(180deg,rgba(255,255,255,0.97),rgba(235,247,235,0.94))] p-6"> 56 + <div className="rounded-[24px] border border-black/8 bg-[#171411] p-6 text-white shadow-[0_24px_70px_rgba(23,20,17,0.26)]"> 57 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[#b9dfb9]">Respondent experience</p> 58 + <div className="mt-8 space-y-6"> 59 + <div className="h-1.5 rounded-full bg-white/10"> 60 + <div className="h-full w-1/2 rounded-full bg-[#9fd7a4]" /> 61 + </div> 62 + <div className="space-y-4"> 63 + <p className="text-sm uppercase tracking-[0.3em] text-white/45">Step 2 of 4</p> 64 + <h2 className="font-display text-4xl leading-tight">What should we call you?</h2> 65 + <p className="max-w-sm text-sm leading-7 text-white/70"> 66 + Every step gets the full stage — large type, clean focus, and motion that keeps the flow moving. 67 + </p> 68 + </div> 69 + <div className="rounded-[24px] border border-white/10 bg-white/5 p-4 text-white/55">Type your answer here…</div> 70 + <div className="flex items-center justify-between"> 71 + <Button variant="secondary" className="border border-white/12 bg-white/8 text-white hover:bg-white/14"> 72 + Back 73 + </Button> 74 + <Button className="bg-[#9fd7a4] text-[#122012] hover:bg-[#b8e4bb]">Continue</Button> 75 + </div> 76 + </div> 77 + </div> 78 + </Card> 79 + </section> 80 + 81 + <section id="surfaces" className="grid gap-6 pb-8 md:grid-cols-3"> 82 + {[ 83 + ["Creator dashboard", "Start new forms, manage drafts, and jump straight into editing without extra ceremony."], 84 + ["Block builder", "A master-detail editor keeps structure simple while the public runner gets the design spotlight."], 85 + ["Response review", "Read each submission in form order and keep the conversational context intact."], 86 + ].map(([title, description]) => ( 87 + <Card key={title} className="p-6"> 88 + <p className="text-xs font-semibold uppercase tracking-[0.26em] text-[var(--accent)]">Surface</p> 89 + <h3 className="mt-4 font-display text-3xl">{title}</h3> 90 + <p className="mt-3 text-sm leading-7 text-[var(--muted)]">{description}</p> 91 + </Card> 92 + ))} 93 + </section> 94 + </main> 95 + ); 96 + }
+1036
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "the-forms", 7 + "dependencies": { 8 + "@auth/prisma-adapter": "^2.11.1", 9 + "@dnd-kit/core": "^6.3.1", 10 + "@dnd-kit/sortable": "^10.0.0", 11 + "@dnd-kit/utilities": "^3.2.2", 12 + "@prisma/client": "6", 13 + "class-variance-authority": "^0.7.1", 14 + "clsx": "^2.1.1", 15 + "framer-motion": "^12.38.0", 16 + "lucide-react": "^1.7.0", 17 + "next": "^16.2.2", 18 + "next-auth": "^4.24.13", 19 + "react": "^19.2.4", 20 + "react-dom": "^19.2.4", 21 + "tailwind-merge": "^3.5.0", 22 + "zod": "^4.3.6", 23 + }, 24 + "devDependencies": { 25 + "@tailwindcss/postcss": "^4.2.2", 26 + "@types/node": "^25.5.2", 27 + "@types/react": "^19.2.14", 28 + "@types/react-dom": "^19.2.3", 29 + "eslint": "9.39.1", 30 + "eslint-config-next": "^16.2.2", 31 + "prisma": "6", 32 + "tailwindcss": "^4.2.2", 33 + "typescript": "^6.0.2", 34 + }, 35 + }, 36 + }, 37 + "packages": { 38 + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], 39 + 40 + "@auth/core": ["@auth/core@0.41.1", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "jose": "^6.0.6", "oauth4webapi": "^3.3.0", "preact": "10.24.3", "preact-render-to-string": "6.5.11" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^7.0.7" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw=="], 41 + 42 + "@auth/prisma-adapter": ["@auth/prisma-adapter@2.11.1", "", { "dependencies": { "@auth/core": "0.41.1" }, "peerDependencies": { "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" } }, "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug=="], 43 + 44 + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], 45 + 46 + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], 47 + 48 + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], 49 + 50 + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], 51 + 52 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], 53 + 54 + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], 55 + 56 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], 57 + 58 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], 59 + 60 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], 61 + 62 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], 63 + 64 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], 65 + 66 + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], 67 + 68 + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], 69 + 70 + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], 71 + 72 + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], 73 + 74 + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], 75 + 76 + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], 77 + 78 + "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="], 79 + 80 + "@dnd-kit/core": ["@dnd-kit/core@6.3.1", "", { "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ=="], 81 + 82 + "@dnd-kit/sortable": ["@dnd-kit/sortable@10.0.0", "", { "dependencies": { "@dnd-kit/utilities": "^3.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.3.0", "react": ">=16.8.0" } }, "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg=="], 83 + 84 + "@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="], 85 + 86 + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], 87 + 88 + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], 89 + 90 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], 91 + 92 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], 93 + 94 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 95 + 96 + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], 97 + 98 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 99 + 100 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 101 + 102 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], 103 + 104 + "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], 105 + 106 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 107 + 108 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 109 + 110 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 111 + 112 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 113 + 114 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 115 + 116 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 117 + 118 + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], 119 + 120 + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], 121 + 122 + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], 123 + 124 + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], 125 + 126 + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], 127 + 128 + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], 129 + 130 + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], 131 + 132 + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], 133 + 134 + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], 135 + 136 + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], 137 + 138 + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], 139 + 140 + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], 141 + 142 + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], 143 + 144 + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], 145 + 146 + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], 147 + 148 + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], 149 + 150 + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], 151 + 152 + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], 153 + 154 + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], 155 + 156 + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], 157 + 158 + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], 159 + 160 + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], 161 + 162 + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], 163 + 164 + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], 165 + 166 + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], 167 + 168 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 169 + 170 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], 171 + 172 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 173 + 174 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 175 + 176 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 177 + 178 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], 179 + 180 + "@next/env": ["@next/env@16.2.2", "", {}, "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ=="], 181 + 182 + "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.2", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-IOPbWzDQ+76AtjZioaCjpIY72xNSDMnarZ2GMQ4wjNLvnJEJHqxQwGFhgnIWLV9klb4g/+amg88Tk5OXVpyLTw=="], 183 + 184 + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg=="], 185 + 186 + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw=="], 187 + 188 + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q=="], 189 + 190 + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg=="], 191 + 192 + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g=="], 193 + 194 + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg=="], 195 + 196 + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g=="], 197 + 198 + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA=="], 199 + 200 + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 201 + 202 + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 203 + 204 + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 205 + 206 + "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], 207 + 208 + "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], 209 + 210 + "@prisma/client": ["@prisma/client@6.19.3", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg=="], 211 + 212 + "@prisma/config": ["@prisma/config@6.19.3", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.21.0", "empathic": "2.0.0" } }, "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ=="], 213 + 214 + "@prisma/debug": ["@prisma/debug@6.19.3", "", {}, "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw=="], 215 + 216 + "@prisma/engines": ["@prisma/engines@6.19.3", "", { "dependencies": { "@prisma/debug": "6.19.3", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/fetch-engine": "6.19.3", "@prisma/get-platform": "6.19.3" } }, "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g=="], 217 + 218 + "@prisma/engines-version": ["@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "", {}, "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA=="], 219 + 220 + "@prisma/fetch-engine": ["@prisma/fetch-engine@6.19.3", "", { "dependencies": { "@prisma/debug": "6.19.3", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/get-platform": "6.19.3" } }, "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA=="], 221 + 222 + "@prisma/get-platform": ["@prisma/get-platform@6.19.3", "", { "dependencies": { "@prisma/debug": "6.19.3" } }, "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA=="], 223 + 224 + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], 225 + 226 + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 227 + 228 + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], 229 + 230 + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], 231 + 232 + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], 233 + 234 + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], 235 + 236 + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], 237 + 238 + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], 239 + 240 + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], 241 + 242 + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], 243 + 244 + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], 245 + 246 + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], 247 + 248 + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], 249 + 250 + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], 251 + 252 + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], 253 + 254 + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], 255 + 256 + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], 257 + 258 + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="], 259 + 260 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 261 + 262 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 263 + 264 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 265 + 266 + "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], 267 + 268 + "@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="], 269 + 270 + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], 271 + 272 + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], 273 + 274 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/type-utils": "8.58.1", "@typescript-eslint/utils": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ=="], 275 + 276 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw=="], 277 + 278 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.1", "@typescript-eslint/types": "^8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g=="], 279 + 280 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1" } }, "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w=="], 281 + 282 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw=="], 283 + 284 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w=="], 285 + 286 + "@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="], 287 + 288 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.1", "@typescript-eslint/tsconfig-utils": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg=="], 289 + 290 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ=="], 291 + 292 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ=="], 293 + 294 + "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], 295 + 296 + "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="], 297 + 298 + "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g=="], 299 + 300 + "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ=="], 301 + 302 + "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.11.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw=="], 303 + 304 + "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw=="], 305 + 306 + "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw=="], 307 + 308 + "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ=="], 309 + 310 + "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w=="], 311 + 312 + "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.11.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA=="], 313 + 314 + "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ=="], 315 + 316 + "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew=="], 317 + 318 + "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.11.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg=="], 319 + 320 + "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w=="], 321 + 322 + "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA=="], 323 + 324 + "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.11.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ=="], 325 + 326 + "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw=="], 327 + 328 + "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ=="], 329 + 330 + "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], 331 + 332 + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], 333 + 334 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 335 + 336 + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], 337 + 338 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 339 + 340 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 341 + 342 + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], 343 + 344 + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], 345 + 346 + "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], 347 + 348 + "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], 349 + 350 + "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="], 351 + 352 + "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], 353 + 354 + "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], 355 + 356 + "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], 357 + 358 + "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], 359 + 360 + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], 361 + 362 + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], 363 + 364 + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], 365 + 366 + "axe-core": ["axe-core@4.11.2", "", {}, "sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw=="], 367 + 368 + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], 369 + 370 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 371 + 372 + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.16", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA=="], 373 + 374 + "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], 375 + 376 + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 377 + 378 + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], 379 + 380 + "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], 381 + 382 + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], 383 + 384 + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 385 + 386 + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], 387 + 388 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 389 + 390 + "caniuse-lite": ["caniuse-lite@1.0.30001787", "", {}, "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg=="], 391 + 392 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 393 + 394 + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], 395 + 396 + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], 397 + 398 + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], 399 + 400 + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], 401 + 402 + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], 403 + 404 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 405 + 406 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 407 + 408 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 409 + 410 + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], 411 + 412 + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 413 + 414 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 415 + 416 + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 417 + 418 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 419 + 420 + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], 421 + 422 + "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], 423 + 424 + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], 425 + 426 + "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], 427 + 428 + "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], 429 + 430 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 431 + 432 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 433 + 434 + "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], 435 + 436 + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], 437 + 438 + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], 439 + 440 + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], 441 + 442 + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], 443 + 444 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 445 + 446 + "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], 447 + 448 + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], 449 + 450 + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 451 + 452 + "effect": ["effect@3.21.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ=="], 453 + 454 + "electron-to-chromium": ["electron-to-chromium@1.5.334", "", {}, "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog=="], 455 + 456 + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], 457 + 458 + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], 459 + 460 + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], 461 + 462 + "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], 463 + 464 + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 465 + 466 + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 467 + 468 + "es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="], 469 + 470 + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 471 + 472 + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], 473 + 474 + "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], 475 + 476 + "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], 477 + 478 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 479 + 480 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 481 + 482 + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], 483 + 484 + "eslint-config-next": ["eslint-config-next@16.2.2", "", { "dependencies": { "@next/eslint-plugin-next": "16.2.2", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-6VlvEhwoug2JpVgjZDhyXrJXUEuPY++TddzIpTaIRvlvlXXFgvQUtm3+Zr84IjFm0lXtJt73w19JA08tOaZVwg=="], 485 + 486 + "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="], 487 + 488 + "eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="], 489 + 490 + "eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="], 491 + 492 + "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="], 493 + 494 + "eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="], 495 + 496 + "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], 497 + 498 + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], 499 + 500 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 501 + 502 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 503 + 504 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 505 + 506 + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], 507 + 508 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 509 + 510 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 511 + 512 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 513 + 514 + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], 515 + 516 + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], 517 + 518 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 519 + 520 + "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], 521 + 522 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 523 + 524 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 525 + 526 + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], 527 + 528 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 529 + 530 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 531 + 532 + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 533 + 534 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 535 + 536 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 537 + 538 + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], 539 + 540 + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], 541 + 542 + "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "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-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], 543 + 544 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 545 + 546 + "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], 547 + 548 + "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], 549 + 550 + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], 551 + 552 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 553 + 554 + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 555 + 556 + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 557 + 558 + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], 559 + 560 + "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="], 561 + 562 + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], 563 + 564 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 565 + 566 + "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], 567 + 568 + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], 569 + 570 + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 571 + 572 + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 573 + 574 + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], 575 + 576 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 577 + 578 + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], 579 + 580 + "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], 581 + 582 + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 583 + 584 + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], 585 + 586 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 587 + 588 + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], 589 + 590 + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], 591 + 592 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 593 + 594 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 595 + 596 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 597 + 598 + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], 599 + 600 + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], 601 + 602 + "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], 603 + 604 + "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], 605 + 606 + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], 607 + 608 + "is-bun-module": ["is-bun-module@2.0.0", "", { "dependencies": { "semver": "^7.7.1" } }, "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ=="], 609 + 610 + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], 611 + 612 + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 613 + 614 + "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], 615 + 616 + "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], 617 + 618 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 619 + 620 + "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], 621 + 622 + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], 623 + 624 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 625 + 626 + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], 627 + 628 + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], 629 + 630 + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 631 + 632 + "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], 633 + 634 + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], 635 + 636 + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], 637 + 638 + "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], 639 + 640 + "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], 641 + 642 + "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], 643 + 644 + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], 645 + 646 + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], 647 + 648 + "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], 649 + 650 + "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], 651 + 652 + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], 653 + 654 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 655 + 656 + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], 657 + 658 + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], 659 + 660 + "jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="], 661 + 662 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 663 + 664 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 665 + 666 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 667 + 668 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 669 + 670 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 671 + 672 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 673 + 674 + "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], 675 + 676 + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], 677 + 678 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 679 + 680 + "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], 681 + 682 + "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], 683 + 684 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 685 + 686 + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], 687 + 688 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], 689 + 690 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], 691 + 692 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], 693 + 694 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], 695 + 696 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], 697 + 698 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], 699 + 700 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], 701 + 702 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], 703 + 704 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], 705 + 706 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], 707 + 708 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], 709 + 710 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 711 + 712 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 713 + 714 + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], 715 + 716 + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], 717 + 718 + "lucide-react": ["lucide-react@1.7.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg=="], 719 + 720 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 721 + 722 + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 723 + 724 + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 725 + 726 + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 727 + 728 + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], 729 + 730 + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 731 + 732 + "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], 733 + 734 + "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], 735 + 736 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 737 + 738 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 739 + 740 + "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], 741 + 742 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 743 + 744 + "next": ["next@16.2.2", "", { "dependencies": { "@next/env": "16.2.2", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.2", "@next/swc-darwin-x64": "16.2.2", "@next/swc-linux-arm64-gnu": "16.2.2", "@next/swc-linux-arm64-musl": "16.2.2", "@next/swc-linux-x64-gnu": "16.2.2", "@next/swc-linux-x64-musl": "16.2.2", "@next/swc-win32-arm64-msvc": "16.2.2", "@next/swc-win32-x64-msvc": "16.2.2", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A=="], 745 + 746 + "next-auth": ["next-auth@4.24.13", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", "cookie": "^0.7.0", "jose": "^4.15.5", "oauth": "^0.9.15", "openid-client": "^5.4.0", "preact": "^10.6.3", "preact-render-to-string": "^5.1.19", "uuid": "^8.3.2" }, "peerDependencies": { "@auth/core": "0.34.3", "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", "nodemailer": "^7.0.7", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, "optionalPeers": ["@auth/core", "nodemailer"] }, "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ=="], 747 + 748 + "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="], 749 + 750 + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], 751 + 752 + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], 753 + 754 + "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="], 755 + 756 + "oauth": ["oauth@0.9.15", "", {}, "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="], 757 + 758 + "oauth4webapi": ["oauth4webapi@3.8.5", "", {}, "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg=="], 759 + 760 + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 761 + 762 + "object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], 763 + 764 + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], 765 + 766 + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], 767 + 768 + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], 769 + 770 + "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], 771 + 772 + "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], 773 + 774 + "object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="], 775 + 776 + "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], 777 + 778 + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], 779 + 780 + "oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="], 781 + 782 + "openid-client": ["openid-client@5.7.1", "", { "dependencies": { "jose": "^4.15.9", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew=="], 783 + 784 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 785 + 786 + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], 787 + 788 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 789 + 790 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 791 + 792 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 793 + 794 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 795 + 796 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 797 + 798 + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 799 + 800 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 801 + 802 + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], 803 + 804 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 805 + 806 + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], 807 + 808 + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], 809 + 810 + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], 811 + 812 + "postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="], 813 + 814 + "preact": ["preact@10.29.1", "", {}, "sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg=="], 815 + 816 + "preact-render-to-string": ["preact-render-to-string@5.2.6", "", { "dependencies": { "pretty-format": "^3.8.0" }, "peerDependencies": { "preact": ">=10" } }, "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw=="], 817 + 818 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 819 + 820 + "pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="], 821 + 822 + "prisma": ["prisma@6.19.3", "", { "dependencies": { "@prisma/config": "6.19.3", "@prisma/engines": "6.19.3" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg=="], 823 + 824 + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], 825 + 826 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 827 + 828 + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], 829 + 830 + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 831 + 832 + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], 833 + 834 + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], 835 + 836 + "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], 837 + 838 + "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], 839 + 840 + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], 841 + 842 + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], 843 + 844 + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], 845 + 846 + "resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="], 847 + 848 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 849 + 850 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 851 + 852 + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 853 + 854 + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 855 + 856 + "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], 857 + 858 + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], 859 + 860 + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], 861 + 862 + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], 863 + 864 + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 865 + 866 + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], 867 + 868 + "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], 869 + 870 + "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], 871 + 872 + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], 873 + 874 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 875 + 876 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 877 + 878 + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], 879 + 880 + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], 881 + 882 + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], 883 + 884 + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], 885 + 886 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 887 + 888 + "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], 889 + 890 + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], 891 + 892 + "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], 893 + 894 + "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], 895 + 896 + "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], 897 + 898 + "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], 899 + 900 + "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], 901 + 902 + "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], 903 + 904 + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], 905 + 906 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 907 + 908 + "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=="], 909 + 910 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 911 + 912 + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 913 + 914 + "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], 915 + 916 + "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], 917 + 918 + "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], 919 + 920 + "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], 921 + 922 + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], 923 + 924 + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 925 + 926 + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], 927 + 928 + "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], 929 + 930 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 931 + 932 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 933 + 934 + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], 935 + 936 + "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], 937 + 938 + "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], 939 + 940 + "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], 941 + 942 + "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], 943 + 944 + "typescript-eslint": ["typescript-eslint@8.58.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg=="], 945 + 946 + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], 947 + 948 + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], 949 + 950 + "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], 951 + 952 + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], 953 + 954 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 955 + 956 + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], 957 + 958 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 959 + 960 + "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], 961 + 962 + "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], 963 + 964 + "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], 965 + 966 + "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], 967 + 968 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 969 + 970 + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], 971 + 972 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 973 + 974 + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], 975 + 976 + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], 977 + 978 + "@auth/core/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], 979 + 980 + "@auth/core/preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="], 981 + 982 + "@auth/core/preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": ">=10" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="], 983 + 984 + "@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 985 + 986 + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 987 + 988 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 989 + 990 + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 991 + 992 + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], 993 + 994 + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], 995 + 996 + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], 997 + 998 + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="], 999 + 1000 + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 1001 + 1002 + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 1003 + 1004 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 1005 + 1006 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], 1007 + 1008 + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 1009 + 1010 + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], 1011 + 1012 + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], 1013 + 1014 + "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], 1015 + 1016 + "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], 1017 + 1018 + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 1019 + 1020 + "is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 1021 + 1022 + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], 1023 + 1024 + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], 1025 + 1026 + "nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], 1027 + 1028 + "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 1029 + 1030 + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 1031 + 1032 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], 1033 + 1034 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], 1035 + } 1036 + }
+21
components.json
··· 1 + { 2 + "$schema": "https://ui.shadcn.com/schema.json", 3 + "style": "new-york", 4 + "rsc": true, 5 + "tsx": true, 6 + "tailwind": { 7 + "config": "", 8 + "css": "app/globals.css", 9 + "baseColor": "neutral", 10 + "cssVariables": true, 11 + "prefix": "" 12 + }, 13 + "aliases": { 14 + "components": "@/components", 15 + "utils": "@/lib/utils", 16 + "ui": "@/components/ui", 17 + "lib": "@/lib", 18 + "hooks": "@/hooks" 19 + }, 20 + "iconLibrary": "lucide" 21 + }
+26
components/auth/google-sign-in-button.tsx
··· 1 + "use client"; 2 + 3 + import { useTransition } from "react"; 4 + import { LoaderCircle, LogIn } from "lucide-react"; 5 + import { signIn } from "next-auth/react"; 6 + 7 + import { Button } from "@/components/ui/button"; 8 + 9 + export function GoogleSignInButton() { 10 + const [isPending, startTransition] = useTransition(); 11 + 12 + return ( 13 + <Button 14 + className="w-full" 15 + size="lg" 16 + onClick={() => 17 + startTransition(() => { 18 + void signIn("google", { callbackUrl: "/dashboard" }); 19 + }) 20 + } 21 + > 22 + {isPending ? <LoaderCircle className="size-4 animate-spin" /> : <LogIn className="size-4" />} 23 + Continue with Google 24 + </Button> 25 + ); 26 + }
+26
components/auth/sign-out-button.tsx
··· 1 + "use client"; 2 + 3 + import { useTransition } from "react"; 4 + import { LoaderCircle, LogOut } from "lucide-react"; 5 + import { signOut } from "next-auth/react"; 6 + 7 + import { Button } from "@/components/ui/button"; 8 + 9 + export function SignOutButton() { 10 + const [isPending, startTransition] = useTransition(); 11 + 12 + return ( 13 + <Button 14 + size="sm" 15 + variant="secondary" 16 + onClick={() => 17 + startTransition(() => { 18 + void signOut({ callbackUrl: "/" }); 19 + }) 20 + } 21 + > 22 + {isPending ? <LoaderCircle className="size-4 animate-spin" /> : <LogOut className="size-4" />} 23 + Sign out 24 + </Button> 25 + ); 26 + }
+14
components/empty-state.tsx
··· 1 + import type { ReactNode } from "react"; 2 + 3 + import { Card } from "@/components/ui/card"; 4 + 5 + export function EmptyState({ eyebrow, title, description, action }: { eyebrow: string; title: string; description: string; action?: ReactNode }) { 6 + return ( 7 + <Card className="border-dashed px-8 py-10 text-center"> 8 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">{eyebrow}</p> 9 + <h3 className="mt-4 font-display text-3xl text-[var(--ink)]">{title}</h3> 10 + <p className="mx-auto mt-3 max-w-lg text-sm leading-7 text-[var(--muted)]">{description}</p> 11 + {action ? <div className="mt-6 flex justify-center">{action}</div> : null} 12 + </Card> 13 + ); 14 + }
+700
components/form-builder.tsx
··· 1 + "use client"; 2 + 3 + import { useEffect, useMemo, useState } from "react"; 4 + import { 5 + closestCenter, 6 + DndContext, 7 + type DragEndEvent, 8 + PointerSensor, 9 + useSensor, 10 + useSensors, 11 + } from "@dnd-kit/core"; 12 + import { 13 + SortableContext, 14 + arrayMove, 15 + useSortable, 16 + verticalListSortingStrategy, 17 + } from "@dnd-kit/sortable"; 18 + import { CSS } from "@dnd-kit/utilities"; 19 + import { 20 + Copy, 21 + GripVertical, 22 + Link as LinkIcon, 23 + LoaderCircle, 24 + Plus, 25 + Radio, 26 + Rows3, 27 + Save, 28 + Settings2, 29 + SquareCheck, 30 + TextCursorInput, 31 + Trash2, 32 + Type, 33 + } from "lucide-react"; 34 + import Link from "next/link"; 35 + import { useRouter } from "next/navigation"; 36 + 37 + import { Badge } from "@/components/ui/badge"; 38 + import { Button } from "@/components/ui/button"; 39 + import { Card } from "@/components/ui/card"; 40 + import { Input } from "@/components/ui/input"; 41 + import { Textarea } from "@/components/ui/textarea"; 42 + import { cn } from "@/lib/utils"; 43 + 44 + type BlockType = "TEXT" | "SHORT_TEXT" | "LONG_TEXT" | "SINGLE_CHOICE" | "MULTIPLE_CHOICE"; 45 + 46 + type TextConfig = { body: string }; 47 + type PlaceholderConfig = { placeholder: string }; 48 + type ChoiceConfig = { options: string[] }; 49 + 50 + type BuilderBlock = { 51 + id: string; 52 + type: BlockType; 53 + title: string; 54 + description: string; 55 + required: boolean; 56 + position: number; 57 + config: TextConfig | PlaceholderConfig | ChoiceConfig; 58 + }; 59 + 60 + type BuilderForm = { 61 + id: string; 62 + title: string; 63 + description: string; 64 + slug: string; 65 + status: "DRAFT" | "PUBLISHED"; 66 + updatedAt: string; 67 + responseCount: number; 68 + blocks: BuilderBlock[]; 69 + }; 70 + 71 + type Selection = { kind: "form" } | { kind: "block"; blockId: string }; 72 + 73 + const blockIcons: Record<BlockType, typeof Type> = { 74 + TEXT: Type, 75 + SHORT_TEXT: TextCursorInput, 76 + LONG_TEXT: Rows3, 77 + SINGLE_CHOICE: Radio, 78 + MULTIPLE_CHOICE: SquareCheck, 79 + }; 80 + 81 + const blockLabels: Record<BlockType, string> = { 82 + TEXT: "Text block", 83 + SHORT_TEXT: "Short text", 84 + LONG_TEXT: "Long text", 85 + SINGLE_CHOICE: "Single choice", 86 + MULTIPLE_CHOICE: "Multiple choice", 87 + }; 88 + 89 + const blockCreationOrder: BlockType[] = ["TEXT", "SHORT_TEXT", "LONG_TEXT", "SINGLE_CHOICE", "MULTIPLE_CHOICE"]; 90 + 91 + async function fetchJson<T>(url: string, init?: RequestInit) { 92 + const response = await fetch(url, { 93 + ...init, 94 + headers: { 95 + "Content-Type": "application/json", 96 + ...(init?.headers ?? {}), 97 + }, 98 + }); 99 + 100 + const payload = (await response.json().catch(() => ({}))) as { error?: string } & T; 101 + 102 + if (!response.ok) { 103 + throw new Error(payload.error ?? "Request failed."); 104 + } 105 + 106 + return payload; 107 + } 108 + 109 + function getBlockPreview(block: BuilderBlock) { 110 + if (block.title.trim()) { 111 + return block.title; 112 + } 113 + 114 + if (block.type === "TEXT") { 115 + return (block.config as TextConfig).body; 116 + } 117 + 118 + if ("placeholder" in block.config) { 119 + return block.config.placeholder; 120 + } 121 + 122 + if ("options" in block.config) { 123 + return block.config.options.join(" • "); 124 + } 125 + 126 + return blockLabels[block.type]; 127 + } 128 + 129 + function isQuestion(type: BlockType) { 130 + return type !== "TEXT"; 131 + } 132 + 133 + function BlockRowInner({ 134 + block, 135 + onSelect, 136 + dragHandle, 137 + }: { 138 + block: BuilderBlock; 139 + onSelect: (blockId: string) => void; 140 + dragHandle: React.ReactNode; 141 + }) { 142 + const Icon = blockIcons[block.type]; 143 + 144 + return ( 145 + <> 146 + {dragHandle} 147 + <button className="min-w-0 flex-1 text-left" type="button" onClick={() => onSelect(block.id)}> 148 + <div className="flex items-center gap-2 text-[var(--muted)]"> 149 + <Icon className="size-4" /> 150 + <span className="text-xs font-semibold uppercase tracking-[0.22em]">{blockLabels[block.type]}</span> 151 + </div> 152 + <p className="mt-2 truncate font-medium text-[var(--ink)]">{getBlockPreview(block)}</p> 153 + <p className="mt-1 truncate text-xs text-[var(--muted)]">{block.description || "No helper copy yet"}</p> 154 + </button> 155 + </> 156 + ); 157 + } 158 + 159 + function SortableBlockRow({ 160 + block, 161 + selected, 162 + onSelect, 163 + }: { 164 + block: BuilderBlock; 165 + selected: boolean; 166 + onSelect: (blockId: string) => void; 167 + }) { 168 + const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: block.id }); 169 + 170 + return ( 171 + <div 172 + ref={setNodeRef} 173 + style={{ 174 + transform: CSS.Transform.toString(transform), 175 + transition, 176 + }} 177 + className={cn( 178 + "group flex items-start gap-3 rounded-[22px] border px-4 py-4 transition", 179 + selected ? "border-[var(--accent)] bg-white shadow-[0_18px_44px_rgba(15,23,42,0.08)]" : "border-black/8 bg-white/65 hover:bg-white", 180 + )} 181 + > 182 + <BlockRowInner 183 + block={block} 184 + onSelect={onSelect} 185 + dragHandle={ 186 + <button 187 + className="mt-1 rounded-full border border-black/8 p-2 text-[var(--muted)] transition hover:bg-black/5" 188 + type="button" 189 + {...attributes} 190 + {...listeners} 191 + > 192 + <GripVertical className="size-4" /> 193 + </button> 194 + } 195 + /> 196 + </div> 197 + ); 198 + } 199 + 200 + function StaticBlockRow({ 201 + block, 202 + selected, 203 + onSelect, 204 + }: { 205 + block: BuilderBlock; 206 + selected: boolean; 207 + onSelect: (blockId: string) => void; 208 + }) { 209 + return ( 210 + <div 211 + className={cn( 212 + "group flex items-start gap-3 rounded-[22px] border px-4 py-4 transition", 213 + selected ? "border-[var(--accent)] bg-white shadow-[0_18px_44px_rgba(15,23,42,0.08)]" : "border-black/8 bg-white/65 hover:bg-white", 214 + )} 215 + > 216 + <BlockRowInner 217 + block={block} 218 + onSelect={onSelect} 219 + dragHandle={ 220 + <div className="mt-1 rounded-full border border-black/8 p-2 text-[var(--muted)]"> 221 + <GripVertical className="size-4" /> 222 + </div> 223 + } 224 + /> 225 + </div> 226 + ); 227 + } 228 + 229 + export function FormBuilder({ initialForm }: { initialForm: BuilderForm }) { 230 + const router = useRouter(); 231 + const [form, setForm] = useState(initialForm); 232 + const [selection, setSelection] = useState<Selection>(() => 233 + initialForm.blocks[0] ? { kind: "block", blockId: initialForm.blocks[0].id } : { kind: "form" }, 234 + ); 235 + const [message, setMessage] = useState<string | null>(null); 236 + const [error, setError] = useState<string | null>(null); 237 + const [busy, setBusy] = useState<string | null>(null); 238 + const [isDndReady, setIsDndReady] = useState(false); 239 + 240 + const [metadataDraft, setMetadataDraft] = useState({ 241 + title: initialForm.title, 242 + description: initialForm.description, 243 + slug: initialForm.slug, 244 + }); 245 + 246 + const selectedBlock = useMemo( 247 + () => (selection.kind === "block" ? form.blocks.find((block) => block.id === selection.blockId) ?? null : null), 248 + [form.blocks, selection], 249 + ); 250 + 251 + const [blockDraft, setBlockDraft] = useState<BuilderBlock | null>(selectedBlock); 252 + 253 + const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } })); 254 + 255 + useEffect(() => { 256 + setMetadataDraft({ 257 + title: form.title, 258 + description: form.description, 259 + slug: form.slug, 260 + }); 261 + }, [form.title, form.description, form.slug]); 262 + 263 + useEffect(() => { 264 + setBlockDraft(selectedBlock); 265 + }, [selectedBlock]); 266 + 267 + useEffect(() => { 268 + setIsDndReady(true); 269 + }, []); 270 + 271 + async function withTask(task: string, runner: () => Promise<void>) { 272 + setBusy(task); 273 + setError(null); 274 + 275 + try { 276 + await runner(); 277 + } catch (caughtError) { 278 + const nextError = caughtError instanceof Error ? caughtError.message : "Something went wrong."; 279 + setError(nextError); 280 + } finally { 281 + setBusy(null); 282 + } 283 + } 284 + 285 + async function saveMetadata() { 286 + await withTask("metadata", async () => { 287 + const payload = await fetchJson<{ form: BuilderForm }>(`/api/forms/${form.id}`, { 288 + method: "PATCH", 289 + body: JSON.stringify(metadataDraft), 290 + }); 291 + 292 + setForm(payload.form); 293 + setMessage("Form settings saved."); 294 + }); 295 + } 296 + 297 + async function addBlock(type: BlockType) { 298 + await withTask(`add-${type}`, async () => { 299 + const payload = await fetchJson<{ block: BuilderBlock }>(`/api/forms/${form.id}/blocks`, { 300 + method: "POST", 301 + body: JSON.stringify({ type }), 302 + }); 303 + 304 + setForm((current) => ({ 305 + ...current, 306 + blocks: [...current.blocks, payload.block], 307 + })); 308 + setSelection({ kind: "block", blockId: payload.block.id }); 309 + setMessage(`${blockLabels[type]} added.`); 310 + }); 311 + } 312 + 313 + async function saveBlock() { 314 + if (!blockDraft) { 315 + return; 316 + } 317 + 318 + await withTask(`block-${blockDraft.id}`, async () => { 319 + const payload = await fetchJson<{ block: BuilderBlock }>(`/api/forms/${form.id}/blocks/${blockDraft.id}`, { 320 + method: "PATCH", 321 + body: JSON.stringify({ 322 + title: blockDraft.title, 323 + description: blockDraft.description, 324 + required: blockDraft.required, 325 + config: blockDraft.config, 326 + }), 327 + }); 328 + 329 + setForm((current) => ({ 330 + ...current, 331 + blocks: current.blocks.map((block) => (block.id === payload.block.id ? payload.block : block)), 332 + })); 333 + setMessage("Block saved."); 334 + }); 335 + } 336 + 337 + async function deleteBlock(blockId: string) { 338 + await withTask(`delete-${blockId}`, async () => { 339 + await fetchJson(`/api/forms/${form.id}/blocks/${blockId}`, { 340 + method: "DELETE", 341 + }); 342 + 343 + setForm((current) => ({ 344 + ...current, 345 + blocks: current.blocks 346 + .filter((block) => block.id !== blockId) 347 + .map((block, index) => ({ 348 + ...block, 349 + position: index, 350 + })), 351 + })); 352 + setSelection({ kind: "form" }); 353 + setMessage("Block removed."); 354 + }); 355 + } 356 + 357 + async function togglePublished() { 358 + await withTask("publish", async () => { 359 + const payload = await fetchJson<{ form: BuilderForm }>(`/api/forms/${form.id}/publish`, { 360 + method: "POST", 361 + body: JSON.stringify({ 362 + published: form.status !== "PUBLISHED", 363 + }), 364 + }); 365 + 366 + setForm(payload.form); 367 + setMessage(payload.form.status === "PUBLISHED" ? "Form published." : "Form moved back to draft."); 368 + router.refresh(); 369 + }); 370 + } 371 + 372 + async function copyShareLink() { 373 + const url = `${window.location.origin}/f/${form.slug}`; 374 + await navigator.clipboard.writeText(url); 375 + setMessage("Share link copied."); 376 + } 377 + 378 + async function handleDragEnd(event: DragEndEvent) { 379 + const { active, over } = event; 380 + 381 + if (!over || active.id === over.id) { 382 + return; 383 + } 384 + 385 + const previous = form.blocks; 386 + const oldIndex = previous.findIndex((block) => block.id === active.id); 387 + const newIndex = previous.findIndex((block) => block.id === over.id); 388 + 389 + if (oldIndex < 0 || newIndex < 0) { 390 + return; 391 + } 392 + 393 + const reordered = arrayMove(previous, oldIndex, newIndex).map((block, index) => ({ ...block, position: index })); 394 + setForm((current) => ({ ...current, blocks: reordered })); 395 + 396 + try { 397 + const payload = await fetchJson<{ form: BuilderForm }>(`/api/forms/${form.id}/blocks/reorder`, { 398 + method: "POST", 399 + body: JSON.stringify({ blockIds: reordered.map((block) => block.id) }), 400 + }); 401 + 402 + setForm(payload.form); 403 + setMessage("Block order updated."); 404 + } catch (caughtError) { 405 + setForm((current) => ({ ...current, blocks: previous })); 406 + setError(caughtError instanceof Error ? caughtError.message : "Could not reorder blocks."); 407 + } 408 + } 409 + 410 + const shareHref = `/f/${form.slug}`; 411 + 412 + return ( 413 + <div className="space-y-6"> 414 + <section className="flex flex-col gap-4 rounded-[30px] border border-black/8 bg-white/70 px-6 py-5 backdrop-blur-sm xl:flex-row xl:items-center xl:justify-between"> 415 + <div> 416 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Builder</p> 417 + <h1 className="mt-3 font-display text-4xl text-[var(--ink)]">{form.title}</h1> 418 + <p className="mt-2 text-sm leading-7 text-[var(--muted)]"> 419 + Left: structure and flow. Right: the selected block or form settings. 420 + </p> 421 + </div> 422 + <div className="flex flex-wrap items-center gap-3"> 423 + <Badge className={cn(form.status === "PUBLISHED" && "bg-[var(--accent-soft)] text-[#2f5d35]")}>{form.status}</Badge> 424 + <Link href={`/forms/${form.id}/responses`}> 425 + <Button variant="secondary">Responses ({form.responseCount})</Button> 426 + </Link> 427 + <Button variant={form.status === "PUBLISHED" ? "secondary" : "default"} onClick={togglePublished}> 428 + {busy === "publish" ? <LoaderCircle className="size-4 animate-spin" /> : <LinkIcon className="size-4" />} 429 + {form.status === "PUBLISHED" ? "Unpublish" : "Publish form"} 430 + </Button> 431 + </div> 432 + </section> 433 + 434 + {(message || error) && ( 435 + <div 436 + className={cn( 437 + "rounded-2xl border px-4 py-3 text-sm", 438 + error ? "border-rose-200 bg-rose-50 text-rose-700" : "border-[#cfe9d0] bg-[#eef9ef] text-[#2f5d35]", 439 + )} 440 + > 441 + {error ?? message} 442 + </div> 443 + )} 444 + 445 + <div className="grid gap-6 lg:grid-cols-[340px_minmax(0,1fr)]"> 446 + <Card className="p-5"> 447 + <div className="flex items-center justify-between gap-3"> 448 + <div> 449 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Flow</p> 450 + <h2 className="mt-2 font-display text-3xl text-[var(--ink)]">Blocks</h2> 451 + </div> 452 + <Badge>{form.blocks.length} steps</Badge> 453 + </div> 454 + 455 + <button 456 + type="button" 457 + onClick={() => setSelection({ kind: "form" })} 458 + className={cn( 459 + "mt-5 flex w-full items-center gap-3 rounded-[22px] border px-4 py-4 text-left transition", 460 + selection.kind === "form" ? "border-[var(--accent)] bg-white shadow-[0_18px_44px_rgba(15,23,42,0.08)]" : "border-black/8 bg-white/60 hover:bg-white", 461 + )} 462 + > 463 + <div className="rounded-full border border-black/8 p-2 text-[var(--muted)]"> 464 + <Settings2 className="size-4" /> 465 + </div> 466 + <div> 467 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Form settings</p> 468 + <p className="mt-1 font-medium text-[var(--ink)]">Title, slug, publish state</p> 469 + </div> 470 + </button> 471 + 472 + <div className="mt-5 space-y-3"> 473 + {isDndReady ? ( 474 + <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> 475 + <SortableContext items={form.blocks.map((block) => block.id)} strategy={verticalListSortingStrategy}> 476 + {form.blocks.map((block) => ( 477 + <SortableBlockRow 478 + key={block.id} 479 + block={block} 480 + selected={selection.kind === "block" && selection.blockId === block.id} 481 + onSelect={(blockId) => setSelection({ kind: "block", blockId })} 482 + /> 483 + ))} 484 + </SortableContext> 485 + </DndContext> 486 + ) : ( 487 + form.blocks.map((block) => ( 488 + <StaticBlockRow 489 + key={block.id} 490 + block={block} 491 + selected={selection.kind === "block" && selection.blockId === block.id} 492 + onSelect={(blockId) => setSelection({ kind: "block", blockId })} 493 + /> 494 + )) 495 + )} 496 + </div> 497 + 498 + <div className="mt-6 space-y-3 border-t border-black/8 pt-6"> 499 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Add block</p> 500 + <div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2"> 501 + {blockCreationOrder.map((type) => { 502 + const Icon = blockIcons[type]; 503 + 504 + return ( 505 + <Button 506 + key={type} 507 + variant="secondary" 508 + size="sm" 509 + className="justify-start" 510 + onClick={() => addBlock(type)} 511 + > 512 + {busy === `add-${type}` ? <LoaderCircle className="size-4 animate-spin" /> : <Icon className="size-4" />} 513 + {blockLabels[type]} 514 + </Button> 515 + ); 516 + })} 517 + </div> 518 + </div> 519 + </Card> 520 + 521 + <Card className="p-6 lg:p-8"> 522 + {selection.kind === "form" ? ( 523 + <div className="space-y-6"> 524 + <div> 525 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Form settings</p> 526 + <h2 className="mt-3 font-display text-4xl text-[var(--ink)]">Shape the live version.</h2> 527 + </div> 528 + 529 + <div className="grid gap-5"> 530 + <label className="space-y-2 text-sm text-[var(--muted)]"> 531 + <span className="font-medium text-[var(--ink)]">Title</span> 532 + <Input value={metadataDraft.title} onChange={(event) => setMetadataDraft((current) => ({ ...current, title: event.target.value }))} /> 533 + </label> 534 + <label className="space-y-2 text-sm text-[var(--muted)]"> 535 + <span className="font-medium text-[var(--ink)]">Description</span> 536 + <Textarea 537 + value={metadataDraft.description} 538 + onChange={(event) => setMetadataDraft((current) => ({ ...current, description: event.target.value }))} 539 + /> 540 + </label> 541 + <label className="space-y-2 text-sm text-[var(--muted)]"> 542 + <span className="font-medium text-[var(--ink)]">Share URL slug</span> 543 + <Input value={metadataDraft.slug} onChange={(event) => setMetadataDraft((current) => ({ ...current, slug: event.target.value }))} /> 544 + </label> 545 + </div> 546 + 547 + <div className="grid gap-4 rounded-[28px] border border-black/8 bg-[var(--bg-strong)] p-5 lg:grid-cols-[1fr_auto_auto] lg:items-center"> 548 + <div> 549 + <p className="text-xs font-semibold uppercase tracking-[0.22em] text-[var(--accent)]">Public route</p> 550 + <p className="mt-2 truncate text-sm text-[var(--ink)]">{shareHref}</p> 551 + <p className="mt-1 text-xs text-[var(--muted)]"> 552 + Only published forms accept anonymous submissions. Editing a published form updates the live experience. 553 + </p> 554 + </div> 555 + <Button variant="secondary" onClick={copyShareLink}> 556 + <Copy className="size-4" /> 557 + Copy link 558 + </Button> 559 + <Link href={shareHref} target="_blank"> 560 + <Button variant="secondary"> 561 + <LinkIcon className="size-4" /> 562 + Open runner 563 + </Button> 564 + </Link> 565 + </div> 566 + 567 + <div className="flex justify-end"> 568 + <Button onClick={saveMetadata}> 569 + {busy === "metadata" ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />} 570 + Save form settings 571 + </Button> 572 + </div> 573 + </div> 574 + ) : blockDraft ? ( 575 + <div className="space-y-6"> 576 + <div className="flex items-start justify-between gap-4"> 577 + <div> 578 + <p className="text-xs font-semibold uppercase tracking-[0.24em] text-[var(--accent)]">Selected block</p> 579 + <h2 className="mt-3 font-display text-4xl text-[var(--ink)]">{blockLabels[blockDraft.type]}</h2> 580 + </div> 581 + <Badge>{blockDraft.position + 1}</Badge> 582 + </div> 583 + 584 + <div className="grid gap-5"> 585 + <label className="space-y-2 text-sm text-[var(--muted)]"> 586 + <span className="font-medium text-[var(--ink)]">Prompt</span> 587 + <Input 588 + value={blockDraft.title} 589 + onChange={(event) => setBlockDraft((current) => (current ? { ...current, title: event.target.value } : current))} 590 + /> 591 + </label> 592 + <label className="space-y-2 text-sm text-[var(--muted)]"> 593 + <span className="font-medium text-[var(--ink)]">Support text</span> 594 + <Textarea 595 + value={blockDraft.description} 596 + onChange={(event) => setBlockDraft((current) => (current ? { ...current, description: event.target.value } : current))} 597 + /> 598 + </label> 599 + 600 + {blockDraft.type === "TEXT" ? ( 601 + <label className="space-y-2 text-sm text-[var(--muted)]"> 602 + <span className="font-medium text-[var(--ink)]">Body copy</span> 603 + <Textarea 604 + value={(blockDraft.config as TextConfig).body} 605 + onChange={(event) => 606 + setBlockDraft((current) => 607 + current ? { ...current, config: { body: event.target.value } } : current, 608 + ) 609 + } 610 + /> 611 + </label> 612 + ) : null} 613 + 614 + {(blockDraft.type === "SHORT_TEXT" || blockDraft.type === "LONG_TEXT") && ( 615 + <label className="space-y-2 text-sm text-[var(--muted)]"> 616 + <span className="font-medium text-[var(--ink)]">Placeholder</span> 617 + <Input 618 + value={(blockDraft.config as PlaceholderConfig).placeholder} 619 + onChange={(event) => 620 + setBlockDraft((current) => 621 + current ? { ...current, config: { placeholder: event.target.value } } : current, 622 + ) 623 + } 624 + /> 625 + </label> 626 + )} 627 + 628 + {(blockDraft.type === "SINGLE_CHOICE" || blockDraft.type === "MULTIPLE_CHOICE") && ( 629 + <label className="space-y-2 text-sm text-[var(--muted)]"> 630 + <span className="font-medium text-[var(--ink)]">Choice options</span> 631 + <Textarea 632 + value={(blockDraft.config as ChoiceConfig).options.join("\n")} 633 + onChange={(event) => 634 + setBlockDraft((current) => 635 + current 636 + ? { 637 + ...current, 638 + config: { 639 + options: event.target.value 640 + .split("\n") 641 + .map((option) => option.trim()) 642 + .filter(Boolean), 643 + }, 644 + } 645 + : current, 646 + ) 647 + } 648 + /> 649 + <p className="text-xs text-[var(--muted)]">Use one option per line. We’ll keep the first 10 non-empty values.</p> 650 + </label> 651 + )} 652 + 653 + {isQuestion(blockDraft.type) ? ( 654 + <label className="flex items-center gap-3 rounded-2xl border border-black/8 bg-[var(--bg-strong)] px-4 py-3 text-sm text-[var(--ink)]"> 655 + <input 656 + checked={blockDraft.required} 657 + className="size-4 rounded border-black/15" 658 + type="checkbox" 659 + onChange={(event) => 660 + setBlockDraft((current) => (current ? { ...current, required: event.target.checked } : current)) 661 + } 662 + /> 663 + Mark this question as required 664 + </label> 665 + ) : null} 666 + </div> 667 + 668 + <div className="flex flex-wrap items-center justify-between gap-3 border-t border-black/8 pt-6"> 669 + <Button variant="danger" onClick={() => deleteBlock(blockDraft.id)}> 670 + {busy === `delete-${blockDraft.id}` ? <LoaderCircle className="size-4 animate-spin" /> : <Trash2 className="size-4" />} 671 + Delete block 672 + </Button> 673 + <Button onClick={saveBlock}> 674 + {busy === `block-${blockDraft.id}` ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />} 675 + Save block 676 + </Button> 677 + </div> 678 + </div> 679 + ) : ( 680 + <div className="flex min-h-[420px] items-center justify-center rounded-[28px] border border-dashed border-black/10 bg-[var(--bg-strong)] p-10 text-center"> 681 + <div> 682 + <p className="text-xs font-semibold uppercase tracking-[0.28em] text-[var(--accent)]">Nothing selected</p> 683 + <h2 className="mt-4 font-display text-4xl text-[var(--ink)]">Pick a block or open form settings.</h2> 684 + <p className="mx-auto mt-3 max-w-lg text-sm leading-7 text-[var(--muted)]"> 685 + The builder stays intentionally simple in v0: organize the flow on the left, edit the selected step on the right. 686 + </p> 687 + <div className="mt-6 flex justify-center"> 688 + <Button variant="secondary" onClick={() => addBlock("SHORT_TEXT")}> 689 + <Plus className="size-4" /> 690 + Add a short text question 691 + </Button> 692 + </div> 693 + </div> 694 + </div> 695 + )} 696 + </Card> 697 + </div> 698 + </div> 699 + ); 700 + }
+13
components/loading-shell.tsx
··· 1 + export function LoadingShell({ title }: { title: string }) { 2 + return ( 3 + <div className="space-y-6"> 4 + <div className="h-4 w-24 animate-pulse rounded-full bg-black/10" /> 5 + <div className="h-12 w-64 animate-pulse rounded-full bg-black/10" /> 6 + <div className="grid gap-6 lg:grid-cols-[320px_minmax(0,1fr)]"> 7 + <div className="h-[420px] animate-pulse rounded-[28px] bg-white/70 shadow-[0_22px_60px_rgba(15,23,42,0.08)]" /> 8 + <div className="h-[420px] animate-pulse rounded-[28px] bg-white/70 shadow-[0_22px_60px_rgba(15,23,42,0.08)]" /> 9 + </div> 10 + <p className="text-sm text-[var(--muted)]">Loading {title}…</p> 11 + </div> 12 + ); 13 + }
+299
components/public-form-runner.tsx
··· 1 + "use client"; 2 + 3 + import { AnimatePresence, motion } from "framer-motion"; 4 + import { ArrowLeft, ArrowRight, Check, LoaderCircle } from "lucide-react"; 5 + import { useMemo, useState } from "react"; 6 + 7 + import { Button } from "@/components/ui/button"; 8 + import { Card } from "@/components/ui/card"; 9 + import { Input } from "@/components/ui/input"; 10 + import { Textarea } from "@/components/ui/textarea"; 11 + import { cn } from "@/lib/utils"; 12 + 13 + type BlockType = "TEXT" | "SHORT_TEXT" | "LONG_TEXT" | "SINGLE_CHOICE" | "MULTIPLE_CHOICE"; 14 + 15 + type PublicBlock = { 16 + id: string; 17 + type: BlockType; 18 + title: string; 19 + description: string; 20 + required: boolean; 21 + position: number; 22 + config: 23 + | { body: string } 24 + | { placeholder: string } 25 + | { options: string[] }; 26 + }; 27 + 28 + type PublicForm = { 29 + id: string; 30 + title: string; 31 + description: string; 32 + slug: string; 33 + blocks: PublicBlock[]; 34 + }; 35 + 36 + async function submitResponse(slug: string, answers: Record<string, string | string[]>) { 37 + const response = await fetch(`/api/public/forms/${slug}/responses`, { 38 + method: "POST", 39 + headers: { 40 + "Content-Type": "application/json", 41 + }, 42 + body: JSON.stringify({ answers }), 43 + }); 44 + 45 + const payload = (await response.json().catch(() => ({}))) as { error?: string; responseId?: string }; 46 + 47 + if (!response.ok) { 48 + throw new Error(payload.error ?? "Could not submit response."); 49 + } 50 + 51 + return payload.responseId; 52 + } 53 + 54 + export function PublicFormRunner({ form }: { form: PublicForm }) { 55 + const [step, setStep] = useState(0); 56 + const [answers, setAnswers] = useState<Record<string, string | string[]>>({}); 57 + const [error, setError] = useState<string | null>(null); 58 + const [isSubmitting, setIsSubmitting] = useState(false); 59 + const [isComplete, setIsComplete] = useState(false); 60 + 61 + const currentBlock = form.blocks[step]; 62 + const progress = useMemo(() => ((step + 1) / form.blocks.length) * 100, [form.blocks.length, step]); 63 + 64 + function setAnswer(blockId: string, value: string | string[]) { 65 + setAnswers((current) => ({ 66 + ...current, 67 + [blockId]: value, 68 + })); 69 + } 70 + 71 + function validateStep() { 72 + if (!currentBlock || currentBlock.type === "TEXT") { 73 + return true; 74 + } 75 + 76 + const value = answers[currentBlock.id]; 77 + 78 + if (!currentBlock.required) { 79 + return true; 80 + } 81 + 82 + if (currentBlock.type === "SHORT_TEXT" || currentBlock.type === "LONG_TEXT") { 83 + if (typeof value === "string" && value.trim()) { 84 + return true; 85 + } 86 + 87 + setError("Please answer before continuing."); 88 + return false; 89 + } 90 + 91 + if (currentBlock.type === "SINGLE_CHOICE") { 92 + if (typeof value === "string" && value.trim()) { 93 + return true; 94 + } 95 + 96 + setError("Choose one option before continuing."); 97 + return false; 98 + } 99 + 100 + if (currentBlock.type === "MULTIPLE_CHOICE") { 101 + if (Array.isArray(value) && value.length > 0) { 102 + return true; 103 + } 104 + 105 + setError("Choose at least one option before continuing."); 106 + return false; 107 + } 108 + 109 + return true; 110 + } 111 + 112 + async function handleContinue() { 113 + setError(null); 114 + 115 + if (!validateStep()) { 116 + return; 117 + } 118 + 119 + if (step === form.blocks.length - 1) { 120 + try { 121 + setIsSubmitting(true); 122 + await submitResponse(form.slug, answers); 123 + setIsComplete(true); 124 + } catch (caughtError) { 125 + setError(caughtError instanceof Error ? caughtError.message : "Could not submit response."); 126 + } finally { 127 + setIsSubmitting(false); 128 + } 129 + 130 + return; 131 + } 132 + 133 + setStep((current) => current + 1); 134 + } 135 + 136 + function handleBack() { 137 + setError(null); 138 + setStep((current) => Math.max(0, current - 1)); 139 + } 140 + 141 + if (isComplete) { 142 + return ( 143 + <Card className="w-full max-w-3xl overflow-hidden bg-[#16120f] p-10 text-white shadow-[0_36px_120px_rgba(22,18,15,0.32)]"> 144 + <p className="text-xs font-semibold uppercase tracking-[0.32em] text-[#b9dfb9]">Response received</p> 145 + <h2 className="mt-6 font-display text-5xl">Thanks for taking the time.</h2> 146 + <p className="mt-4 max-w-2xl text-base leading-8 text-white/72"> 147 + Your response was submitted anonymously. The creator can review your answers, but they are not linked to a 148 + login or account. 149 + </p> 150 + </Card> 151 + ); 152 + } 153 + 154 + return ( 155 + <Card className="w-full max-w-4xl overflow-hidden bg-[#16120f] p-4 text-white shadow-[0_36px_120px_rgba(22,18,15,0.32)] sm:p-6 lg:p-8"> 156 + <div className="rounded-[28px] border border-white/10 bg-white/[0.03] p-6 sm:p-8"> 157 + <div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> 158 + <div> 159 + <p className="text-xs font-semibold uppercase tracking-[0.3em] text-[#b9dfb9]">{form.title}</p> 160 + {form.description ? <p className="mt-2 max-w-xl text-sm leading-7 text-white/62">{form.description}</p> : null} 161 + </div> 162 + <div className="sm:min-w-[180px]"> 163 + <div className="h-2 rounded-full bg-white/10"> 164 + <motion.div 165 + className="h-full rounded-full bg-[#9fd7a4]" 166 + animate={{ width: `${progress}%` }} 167 + transition={{ type: "spring", stiffness: 130, damping: 20 }} 168 + /> 169 + </div> 170 + <p className="mt-2 text-right text-xs uppercase tracking-[0.24em] text-white/45"> 171 + Step {step + 1} of {form.blocks.length} 172 + </p> 173 + </div> 174 + </div> 175 + 176 + <AnimatePresence mode="wait"> 177 + <motion.div 178 + key={currentBlock.id} 179 + initial={{ opacity: 0, y: 18 }} 180 + animate={{ opacity: 1, y: 0 }} 181 + exit={{ opacity: 0, y: -18 }} 182 + transition={{ duration: 0.25, ease: "easeOut" }} 183 + className="min-h-[360px]" 184 + > 185 + {currentBlock.type === "TEXT" ? ( 186 + <div className="flex min-h-[320px] flex-col justify-center"> 187 + <p className="text-xs font-semibold uppercase tracking-[0.3em] text-white/38">Context</p> 188 + <h2 className="mt-5 font-display text-4xl leading-tight sm:text-6xl">{currentBlock.title || "Take a breath before the next step."}</h2> 189 + {currentBlock.description ? <p className="mt-5 max-w-2xl text-base leading-8 text-white/72">{currentBlock.description}</p> : null} 190 + <p className="mt-6 max-w-2xl text-lg leading-9 text-white/78">{(currentBlock.config as { body: string }).body}</p> 191 + </div> 192 + ) : ( 193 + <div className="flex min-h-[320px] flex-col justify-center"> 194 + <p className="text-xs font-semibold uppercase tracking-[0.3em] text-white/38">Question</p> 195 + <h2 className="mt-5 font-display text-4xl leading-tight sm:text-6xl">{currentBlock.title}</h2> 196 + {currentBlock.description ? <p className="mt-5 max-w-2xl text-base leading-8 text-white/72">{currentBlock.description}</p> : null} 197 + 198 + <div className="mt-10 space-y-4"> 199 + {currentBlock.type === "SHORT_TEXT" ? ( 200 + <Input 201 + className="h-14 rounded-[24px] border-white/10 bg-white/6 text-lg text-white placeholder:text-white/28" 202 + placeholder={(currentBlock.config as { placeholder: string }).placeholder} 203 + value={typeof answers[currentBlock.id] === "string" ? answers[currentBlock.id] : ""} 204 + onChange={(event) => setAnswer(currentBlock.id, event.target.value)} 205 + /> 206 + ) : null} 207 + 208 + {currentBlock.type === "LONG_TEXT" ? ( 209 + <Textarea 210 + className="min-h-[180px] rounded-[28px] border-white/10 bg-white/6 text-lg text-white placeholder:text-white/28" 211 + placeholder={(currentBlock.config as { placeholder: string }).placeholder} 212 + value={typeof answers[currentBlock.id] === "string" ? answers[currentBlock.id] : ""} 213 + onChange={(event) => setAnswer(currentBlock.id, event.target.value)} 214 + /> 215 + ) : null} 216 + 217 + {currentBlock.type === "SINGLE_CHOICE" ? ( 218 + <div className="grid gap-3"> 219 + {(currentBlock.config as { options: string[] }).options.map((option) => { 220 + const selected = answers[currentBlock.id] === option; 221 + 222 + return ( 223 + <button 224 + key={option} 225 + type="button" 226 + className={cn( 227 + "flex items-center justify-between rounded-[22px] border px-5 py-4 text-left transition", 228 + selected 229 + ? "border-[#9fd7a4] bg-[#9fd7a4]/16 text-white" 230 + : "border-white/10 bg-white/4 text-white/78 hover:bg-white/8", 231 + )} 232 + onClick={() => setAnswer(currentBlock.id, option)} 233 + > 234 + <span>{option}</span> 235 + {selected ? <Check className="size-4" /> : null} 236 + </button> 237 + ); 238 + })} 239 + </div> 240 + ) : null} 241 + 242 + {currentBlock.type === "MULTIPLE_CHOICE" ? ( 243 + <div className="grid gap-3"> 244 + {(currentBlock.config as { options: string[] }).options.map((option) => { 245 + const rawValues = answers[currentBlock.id]; 246 + const currentValues: string[] = Array.isArray(rawValues) ? rawValues : []; 247 + const selected = currentValues.includes(option); 248 + 249 + return ( 250 + <button 251 + key={option} 252 + type="button" 253 + className={cn( 254 + "flex items-center justify-between rounded-[22px] border px-5 py-4 text-left transition", 255 + selected 256 + ? "border-[#9fd7a4] bg-[#9fd7a4]/16 text-white" 257 + : "border-white/10 bg-white/4 text-white/78 hover:bg-white/8", 258 + )} 259 + onClick={() => { 260 + const nextValues = selected 261 + ? currentValues.filter((value) => value !== option) 262 + : [...currentValues, option]; 263 + setAnswer(currentBlock.id, nextValues); 264 + }} 265 + > 266 + <span>{option}</span> 267 + {selected ? <Check className="size-4" /> : null} 268 + </button> 269 + ); 270 + })} 271 + </div> 272 + ) : null} 273 + </div> 274 + 275 + {currentBlock.required ? ( 276 + <p className="mt-4 text-xs font-semibold uppercase tracking-[0.24em] text-[#b9dfb9]">Required</p> 277 + ) : null} 278 + </div> 279 + )} 280 + </motion.div> 281 + </AnimatePresence> 282 + 283 + {error ? <p className="mt-4 text-sm text-[#f8b4b4]">{error}</p> : null} 284 + 285 + <div className="mt-8 flex items-center justify-between gap-3"> 286 + <Button variant="secondary" className="border border-white/10 bg-white/5 text-white hover:bg-white/12" onClick={handleBack} disabled={step === 0 || isSubmitting}> 287 + <ArrowLeft className="size-4" /> 288 + Back 289 + </Button> 290 + <Button className="bg-[#9fd7a4] text-[#122012] hover:bg-[#b8e4bb]" onClick={handleContinue} disabled={isSubmitting}> 291 + {isSubmitting ? <LoaderCircle className="size-4 animate-spin" /> : null} 292 + {step === form.blocks.length - 1 ? "Submit response" : "Continue"} 293 + {!isSubmitting ? <ArrowRight className="size-4" /> : null} 294 + </Button> 295 + </div> 296 + </div> 297 + </Card> 298 + ); 299 + }
+15
components/ui/badge.tsx
··· 1 + import type { HTMLAttributes } from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + export function Badge({ className, ...props }: HTMLAttributes<HTMLSpanElement>) { 6 + return ( 7 + <span 8 + className={cn( 9 + "inline-flex items-center rounded-full border border-black/8 bg-black/5 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-[var(--muted)]", 10 + className, 11 + )} 12 + {...props} 13 + /> 14 + ); 15 + }
+45
components/ui/button.tsx
··· 1 + import * as React from "react"; 2 + import { cva, type VariantProps } from "class-variance-authority"; 3 + 4 + import { cn } from "@/lib/utils"; 5 + 6 + const buttonVariants = cva( 7 + "inline-flex items-center justify-center gap-2 rounded-full text-sm font-semibold transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-[var(--bg)]", 8 + { 9 + variants: { 10 + variant: { 11 + default: 12 + "bg-[var(--ink)] px-5 py-3 text-[var(--bg)] shadow-[0_16px_40px_rgba(15,23,42,0.18)] hover:-translate-y-0.5 hover:shadow-[0_20px_48px_rgba(15,23,42,0.24)] focus-visible:ring-[var(--ink)]", 13 + secondary: 14 + "bg-white/70 px-5 py-3 text-[var(--ink)] ring-1 ring-black/10 hover:bg-white focus-visible:ring-[var(--ink)]", 15 + ghost: 16 + "px-3 py-2 text-[var(--muted)] hover:bg-black/5 hover:text-[var(--ink)] focus-visible:ring-[var(--ink)]", 17 + danger: 18 + "bg-[#f87171] px-5 py-3 text-white shadow-[0_12px_28px_rgba(248,113,113,0.28)] hover:bg-[#ef4444] focus-visible:ring-[#ef4444]", 19 + }, 20 + size: { 21 + default: "h-11", 22 + sm: "h-9 px-4 text-xs", 23 + lg: "h-12 px-6 text-base", 24 + icon: "h-10 w-10 rounded-full", 25 + }, 26 + }, 27 + defaultVariants: { 28 + variant: "default", 29 + size: "default", 30 + }, 31 + }, 32 + ); 33 + 34 + export interface ButtonProps 35 + extends React.ButtonHTMLAttributes<HTMLButtonElement>, 36 + VariantProps<typeof buttonVariants> {} 37 + 38 + const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 39 + ({ className, variant, size, type = "button", ...props }, ref) => { 40 + return <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} type={type} {...props} />; 41 + }, 42 + ); 43 + Button.displayName = "Button"; 44 + 45 + export { Button, buttonVariants };
+15
components/ui/card.tsx
··· 1 + import type { HTMLAttributes } from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + export function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) { 6 + return ( 7 + <div 8 + className={cn( 9 + "rounded-[28px] border border-black/8 bg-white/85 shadow-[0_22px_60px_rgba(15,23,42,0.08)] backdrop-blur-sm", 10 + className, 11 + )} 12 + {...props} 13 + /> 14 + ); 15 + }
+19
components/ui/input.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + export const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>( 6 + ({ className, ...props }, ref) => { 7 + return ( 8 + <input 9 + ref={ref} 10 + className={cn( 11 + "flex h-11 w-full rounded-2xl border border-black/10 bg-white px-4 py-3 text-sm text-[var(--ink)] shadow-[inset_0_1px_0_rgba(255,255,255,0.5)] outline-none transition focus:border-black/20 focus:ring-2 focus:ring-black/5", 12 + className, 13 + )} 14 + {...props} 15 + /> 16 + ); 17 + }, 18 + ); 19 + Input.displayName = "Input";
+19
components/ui/textarea.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + export const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>( 6 + ({ className, ...props }, ref) => { 7 + return ( 8 + <textarea 9 + ref={ref} 10 + className={cn( 11 + "flex min-h-[120px] w-full rounded-3xl border border-black/10 bg-white px-4 py-3 text-sm text-[var(--ink)] shadow-[inset_0_1px_0_rgba(255,255,255,0.5)] outline-none transition focus:border-black/20 focus:ring-2 focus:ring-black/5", 12 + className, 13 + )} 14 + {...props} 15 + /> 16 + ); 17 + }, 18 + ); 19 + Textarea.displayName = "Textarea";
+21
compose.yaml
··· 1 + services: 2 + postgres: 3 + image: docker.io/library/postgres:16-alpine 4 + container_name: the-forms-postgres 5 + restart: unless-stopped 6 + environment: 7 + POSTGRES_DB: the_forms 8 + POSTGRES_USER: the_forms 9 + POSTGRES_PASSWORD: the_forms 10 + ports: 11 + - "5432:5432" 12 + volumes: 13 + - postgres-data:/var/lib/postgresql/data 14 + healthcheck: 15 + test: ["CMD-SHELL", "pg_isready -U the_forms -d the_forms"] 16 + interval: 10s 17 + timeout: 5s 18 + retries: 5 19 + 20 + volumes: 21 + postgres-data:
+18
eslint.config.mjs
··· 1 + import { defineConfig, globalIgnores } from "eslint/config"; 2 + import nextVitals from "eslint-config-next/core-web-vitals"; 3 + import nextTs from "eslint-config-next/typescript"; 4 + 5 + const eslintConfig = defineConfig([ 6 + ...nextVitals, 7 + ...nextTs, 8 + // Override default ignores of eslint-config-next. 9 + globalIgnores([ 10 + // Default ignores of eslint-config-next: 11 + ".next/**", 12 + "out/**", 13 + "build/**", 14 + "next-env.d.ts", 15 + ]), 16 + ]); 17 + 18 + export default eslintConfig;
+17
lib/api.ts
··· 1 + import { NextResponse } from "next/server"; 2 + import { ZodError } from "zod"; 3 + 4 + import { AppError } from "@/lib/errors"; 5 + 6 + export function handleRouteError(error: unknown) { 7 + if (error instanceof AppError) { 8 + return NextResponse.json({ error: error.message }, { status: error.status }); 9 + } 10 + 11 + if (error instanceof ZodError) { 12 + return NextResponse.json({ error: error.issues[0]?.message ?? "Invalid request payload." }, { status: 422 }); 13 + } 14 + 15 + console.error(error); 16 + return NextResponse.json({ error: "Unexpected server error." }, { status: 500 }); 17 + }
+36
lib/auth.ts
··· 1 + import { PrismaAdapter } from "@auth/prisma-adapter"; 2 + import type { Adapter } from "next-auth/adapters"; 3 + import { getServerSession, type NextAuthOptions } from "next-auth"; 4 + import GoogleProvider from "next-auth/providers/google"; 5 + 6 + import { db } from "@/lib/db"; 7 + 8 + export const authOptions: NextAuthOptions = { 9 + adapter: PrismaAdapter(db) as Adapter, 10 + secret: process.env.AUTH_SECRET, 11 + session: { 12 + strategy: "database", 13 + }, 14 + pages: { 15 + signIn: "/login", 16 + }, 17 + providers: [ 18 + GoogleProvider({ 19 + clientId: process.env.AUTH_GOOGLE_ID ?? "", 20 + clientSecret: process.env.AUTH_GOOGLE_SECRET ?? "", 21 + }), 22 + ], 23 + callbacks: { 24 + async session({ session, user }) { 25 + if (session.user) { 26 + session.user.id = user.id; 27 + } 28 + 29 + return session; 30 + }, 31 + }, 32 + }; 33 + 34 + export function getServerAuthSession() { 35 + return getServerSession(authOptions); 36 + }
+105
lib/blocks.ts
··· 1 + import { FormBlockType, type FormBlock } from "@prisma/client"; 2 + import { z } from "zod"; 3 + 4 + export const blockTypeLabels: Record<FormBlockType, string> = { 5 + TEXT: "Text block", 6 + SHORT_TEXT: "Short text", 7 + LONG_TEXT: "Long text", 8 + SINGLE_CHOICE: "Single choice", 9 + MULTIPLE_CHOICE: "Multiple choice", 10 + }; 11 + 12 + const textConfigSchema = z.object({ 13 + body: z.string().max(2400).default("Introduce the next part of your form."), 14 + }); 15 + 16 + const shortTextConfigSchema = z.object({ 17 + placeholder: z.string().max(120).default("Type your answer here…"), 18 + }); 19 + 20 + const longTextConfigSchema = z.object({ 21 + placeholder: z.string().max(240).default("Tell us a little more…"), 22 + }); 23 + 24 + const optionListSchema = z.object({ 25 + options: z.array(z.string().min(1).max(120)).min(2).max(10).default(["Option 1", "Option 2"]), 26 + }); 27 + 28 + export type TextBlockConfig = z.infer<typeof textConfigSchema>; 29 + export type ShortTextBlockConfig = z.infer<typeof shortTextConfigSchema>; 30 + export type LongTextBlockConfig = z.infer<typeof longTextConfigSchema>; 31 + export type ChoiceBlockConfig = z.infer<typeof optionListSchema>; 32 + export type BlockConfig = 33 + | TextBlockConfig 34 + | ShortTextBlockConfig 35 + | LongTextBlockConfig 36 + | ChoiceBlockConfig; 37 + 38 + export type SerializedBlock = Omit<FormBlock, "config"> & { 39 + config: BlockConfig; 40 + }; 41 + 42 + export function isQuestionBlock(type: FormBlockType) { 43 + return type !== FormBlockType.TEXT; 44 + } 45 + 46 + export function getDefaultBlockConfig(type: FormBlockType): BlockConfig { 47 + switch (type) { 48 + case FormBlockType.TEXT: 49 + return textConfigSchema.parse({}); 50 + case FormBlockType.SHORT_TEXT: 51 + return shortTextConfigSchema.parse({}); 52 + case FormBlockType.LONG_TEXT: 53 + return longTextConfigSchema.parse({}); 54 + case FormBlockType.SINGLE_CHOICE: 55 + case FormBlockType.MULTIPLE_CHOICE: 56 + return optionListSchema.parse({}); 57 + default: 58 + return textConfigSchema.parse({}); 59 + } 60 + } 61 + 62 + export function parseBlockConfig(type: FormBlockType, value: unknown): BlockConfig { 63 + switch (type) { 64 + case FormBlockType.TEXT: 65 + return textConfigSchema.parse(value ?? {}); 66 + case FormBlockType.SHORT_TEXT: 67 + return shortTextConfigSchema.parse(value ?? {}); 68 + case FormBlockType.LONG_TEXT: 69 + return longTextConfigSchema.parse(value ?? {}); 70 + case FormBlockType.SINGLE_CHOICE: 71 + case FormBlockType.MULTIPLE_CHOICE: 72 + return optionListSchema.parse(value ?? {}); 73 + default: 74 + return textConfigSchema.parse(value ?? {}); 75 + } 76 + } 77 + 78 + export function serializeBlock(block: FormBlock): SerializedBlock { 79 + return { 80 + ...block, 81 + config: parseBlockConfig(block.type, block.config), 82 + }; 83 + } 84 + 85 + export function getBlockPreview(block: Pick<FormBlock, "type" | "title" | "description" | "config">) { 86 + const config = parseBlockConfig(block.type, block.config); 87 + 88 + if (block.title.trim()) { 89 + return block.title; 90 + } 91 + 92 + if (block.type === FormBlockType.TEXT && "body" in config) { 93 + return config.body; 94 + } 95 + 96 + if ("placeholder" in config && config.placeholder.trim()) { 97 + return config.placeholder; 98 + } 99 + 100 + if ("options" in config) { 101 + return config.options.join(" • "); 102 + } 103 + 104 + return block.description || blockTypeLabels[block.type]; 105 + }
+15
lib/db.ts
··· 1 + import { PrismaClient } from "@prisma/client"; 2 + 3 + const globalForPrisma = globalThis as unknown as { 4 + prisma?: PrismaClient; 5 + }; 6 + 7 + export const db = 8 + globalForPrisma.prisma ?? 9 + new PrismaClient({ 10 + log: process.env.NODE_ENV === "development" ? ["warn", "error"] : ["error"], 11 + }); 12 + 13 + if (process.env.NODE_ENV !== "production") { 14 + globalForPrisma.prisma = db; 15 + }
+9
lib/errors.ts
··· 1 + export class AppError extends Error { 2 + status: number; 3 + 4 + constructor(message: string, status = 400) { 5 + super(message); 6 + this.name = "AppError"; 7 + this.status = status; 8 + } 9 + }
+651
lib/forms.ts
··· 1 + import { 2 + FormBlockType, 3 + FormStatus, 4 + type Form, 5 + type FormBlock, 6 + type Prisma, 7 + } from "@prisma/client"; 8 + import { z } from "zod"; 9 + 10 + import { 11 + getDefaultBlockConfig, 12 + isQuestionBlock, 13 + parseBlockConfig, 14 + serializeBlock, 15 + type BlockConfig, 16 + type SerializedBlock, 17 + } from "@/lib/blocks"; 18 + import { db } from "@/lib/db"; 19 + import { AppError } from "@/lib/errors"; 20 + import { 21 + blockUpdateSchema, 22 + createBlockSchema, 23 + formMetadataSchema, 24 + publishFormSchema, 25 + reorderBlocksSchema, 26 + } from "@/lib/validators"; 27 + import { slugify } from "@/lib/utils"; 28 + 29 + const builderFormInclude = { 30 + blocks: { 31 + orderBy: { 32 + position: "asc", 33 + }, 34 + }, 35 + _count: { 36 + select: { 37 + responses: true, 38 + }, 39 + }, 40 + } satisfies Prisma.FormInclude; 41 + 42 + type BuilderFormRecord = Prisma.FormGetPayload<{ 43 + include: typeof builderFormInclude; 44 + }>; 45 + 46 + type ResponseRecord = Prisma.ResponseGetPayload<{ 47 + include: { 48 + form: { 49 + include: { 50 + blocks: { 51 + orderBy: { 52 + position: "asc"; 53 + }; 54 + }; 55 + }; 56 + }; 57 + }; 58 + }>; 59 + 60 + const snapshotBlockSchema = z.object({ 61 + id: z.string(), 62 + type: z.nativeEnum(FormBlockType), 63 + title: z.string(), 64 + description: z.string(), 65 + required: z.boolean(), 66 + position: z.number(), 67 + config: z.unknown(), 68 + }); 69 + 70 + export type BuilderForm = { 71 + id: string; 72 + title: string; 73 + description: string; 74 + slug: string; 75 + status: FormStatus; 76 + updatedAt: string; 77 + responseCount: number; 78 + blocks: SerializedBlock[]; 79 + }; 80 + 81 + export type PublicForm = { 82 + id: string; 83 + title: string; 84 + description: string; 85 + slug: string; 86 + blocks: SerializedBlock[]; 87 + }; 88 + 89 + export type FormListItem = { 90 + id: string; 91 + title: string; 92 + description: string; 93 + slug: string; 94 + status: FormStatus; 95 + updatedAt: string; 96 + responseCount: number; 97 + }; 98 + 99 + export type ResponseListItem = { 100 + id: string; 101 + submittedAt: string; 102 + answerCount: number; 103 + }; 104 + 105 + export type ResponseDetail = { 106 + id: string; 107 + submittedAt: string; 108 + answers: Record<string, string | string[]>; 109 + blocks: SerializedBlock[]; 110 + }; 111 + 112 + function toBuilderForm(form: BuilderFormRecord): BuilderForm { 113 + return { 114 + id: form.id, 115 + title: form.title, 116 + description: form.description, 117 + slug: form.slug, 118 + status: form.status, 119 + updatedAt: form.updatedAt.toISOString(), 120 + responseCount: form._count.responses, 121 + blocks: form.blocks.map(serializeBlock), 122 + }; 123 + } 124 + 125 + function toPublicForm(form: Form & { blocks: FormBlock[] }): PublicForm { 126 + return { 127 + id: form.id, 128 + title: form.title, 129 + description: form.description, 130 + slug: form.slug, 131 + blocks: form.blocks.map(serializeBlock), 132 + }; 133 + } 134 + 135 + function initialBlocks(): Prisma.FormBlockCreateWithoutFormInput[] { 136 + return [ 137 + { 138 + type: FormBlockType.TEXT, 139 + title: "Welcome", 140 + description: "Set the scene before people start answering.", 141 + position: 0, 142 + required: false, 143 + config: getDefaultBlockConfig(FormBlockType.TEXT), 144 + }, 145 + { 146 + type: FormBlockType.SHORT_TEXT, 147 + title: "What should we call you?", 148 + description: "A simple first question keeps the flow moving.", 149 + position: 1, 150 + required: true, 151 + config: getDefaultBlockConfig(FormBlockType.SHORT_TEXT), 152 + }, 153 + ]; 154 + } 155 + 156 + async function ensureUniqueSlug(slug: string, exceptFormId?: string) { 157 + const existing = await db.form.findUnique({ 158 + where: { slug }, 159 + select: { id: true }, 160 + }); 161 + 162 + if (existing && existing.id !== exceptFormId) { 163 + throw new AppError("That share URL is already taken.", 409); 164 + } 165 + } 166 + 167 + async function createUniqueSlug(seed: string) { 168 + const base = slugify(seed) || "form"; 169 + let slug = base; 170 + let attempt = 0; 171 + 172 + while (attempt < 20) { 173 + const existing = await db.form.findUnique({ 174 + where: { slug }, 175 + select: { id: true }, 176 + }); 177 + 178 + if (!existing) { 179 + return slug; 180 + } 181 + 182 + attempt += 1; 183 + slug = `${base}-${Math.random().toString(36).slice(2, 6)}`; 184 + } 185 + 186 + return `${base}-${crypto.randomUUID().slice(0, 8)}`; 187 + } 188 + 189 + async function assertOwner(userId: string, formId: string) { 190 + const form = await db.form.findFirst({ 191 + where: { 192 + id: formId, 193 + userId, 194 + }, 195 + include: builderFormInclude, 196 + }); 197 + 198 + if (!form) { 199 + throw new AppError("Form not found.", 404); 200 + } 201 + 202 + return form; 203 + } 204 + 205 + function sanitizeOptions(options: string[]) { 206 + return options 207 + .map((option) => option.trim()) 208 + .filter(Boolean) 209 + .slice(0, 10); 210 + } 211 + 212 + function normalizeBlockConfig(type: FormBlockType, value: unknown): BlockConfig { 213 + const config = parseBlockConfig(type, value); 214 + 215 + if ((type === FormBlockType.SINGLE_CHOICE || type === FormBlockType.MULTIPLE_CHOICE) && "options" in config) { 216 + const options = sanitizeOptions(config.options); 217 + 218 + return { 219 + options: options.length >= 2 ? options : ["Option 1", "Option 2"], 220 + }; 221 + } 222 + 223 + return config; 224 + } 225 + 226 + function normalizeAnswer(block: SerializedBlock, rawValue: unknown): string | string[] | undefined { 227 + const config = block.config; 228 + 229 + if (!isQuestionBlock(block.type)) { 230 + return undefined; 231 + } 232 + 233 + if (block.type === FormBlockType.SHORT_TEXT || block.type === FormBlockType.LONG_TEXT) { 234 + const value = typeof rawValue === "string" ? rawValue.trim() : ""; 235 + 236 + if (!value) { 237 + if (block.required) { 238 + throw new AppError(`Please answer “${block.title || "this question"}”.`, 422); 239 + } 240 + 241 + return undefined; 242 + } 243 + 244 + return value; 245 + } 246 + 247 + if (block.type === FormBlockType.SINGLE_CHOICE) { 248 + const value = typeof rawValue === "string" ? rawValue.trim() : ""; 249 + const options = "options" in config ? config.options : []; 250 + 251 + if (!value) { 252 + if (block.required) { 253 + throw new AppError(`Please choose an option for “${block.title || "this question"}”.`, 422); 254 + } 255 + 256 + return undefined; 257 + } 258 + 259 + if (!options.includes(value)) { 260 + throw new AppError(`Invalid option submitted for “${block.title || "this question"}”.`, 422); 261 + } 262 + 263 + return value; 264 + } 265 + 266 + if (block.type === FormBlockType.MULTIPLE_CHOICE) { 267 + const values = Array.isArray(rawValue) 268 + ? rawValue.filter((value): value is string => typeof value === "string") 269 + : []; 270 + const options = "options" in config ? config.options : []; 271 + const uniqueValues = [...new Set(values.map((value) => value.trim()).filter(Boolean))]; 272 + 273 + if (!uniqueValues.length) { 274 + if (block.required) { 275 + throw new AppError(`Please choose at least one option for “${block.title || "this question"}”.`, 422); 276 + } 277 + 278 + return undefined; 279 + } 280 + 281 + if (!uniqueValues.every((value) => options.includes(value))) { 282 + throw new AppError(`Invalid option submitted for “${block.title || "this question"}”.`, 422); 283 + } 284 + 285 + return uniqueValues; 286 + } 287 + 288 + return undefined; 289 + } 290 + 291 + function parseSnapshotBlocks(response: ResponseRecord): SerializedBlock[] { 292 + const parsed = z.array(snapshotBlockSchema).safeParse(response.formSnapshotJson); 293 + 294 + if (!parsed.success) { 295 + return response.form.blocks.map(serializeBlock); 296 + } 297 + 298 + return parsed.data 299 + .sort((left, right) => left.position - right.position) 300 + .map((block) => ({ 301 + id: block.id, 302 + type: block.type, 303 + title: block.title, 304 + description: block.description, 305 + required: block.required, 306 + position: block.position, 307 + formId: response.formId, 308 + createdAt: response.submittedAt, 309 + updatedAt: response.submittedAt, 310 + config: normalizeBlockConfig(block.type, block.config), 311 + })); 312 + } 313 + 314 + export async function listFormsForOwner(userId: string): Promise<FormListItem[]> { 315 + const forms = await db.form.findMany({ 316 + where: { userId }, 317 + include: { 318 + _count: { 319 + select: { 320 + responses: true, 321 + }, 322 + }, 323 + }, 324 + orderBy: { 325 + updatedAt: "desc", 326 + }, 327 + }); 328 + 329 + return forms.map((form) => ({ 330 + id: form.id, 331 + title: form.title, 332 + description: form.description, 333 + slug: form.slug, 334 + status: form.status, 335 + updatedAt: form.updatedAt.toISOString(), 336 + responseCount: form._count.responses, 337 + })); 338 + } 339 + 340 + export async function createDraftForm(userId: string) { 341 + const slug = await createUniqueSlug(`untitled-form-${Math.random().toString(36).slice(2, 6)}`); 342 + 343 + const form = await db.form.create({ 344 + data: { 345 + userId, 346 + title: "Untitled form", 347 + description: "", 348 + slug, 349 + blocks: { 350 + create: initialBlocks(), 351 + }, 352 + }, 353 + include: builderFormInclude, 354 + }); 355 + 356 + return toBuilderForm(form); 357 + } 358 + 359 + export async function getOwnedFormForBuilder(userId: string, formId: string) { 360 + const form = await assertOwner(userId, formId); 361 + return toBuilderForm(form); 362 + } 363 + 364 + export async function updateOwnedFormMetadata(userId: string, formId: string, payload: unknown) { 365 + const parsed = formMetadataSchema.parse(payload); 366 + await assertOwner(userId, formId); 367 + await ensureUniqueSlug(parsed.slug, formId); 368 + 369 + const form = await db.form.update({ 370 + where: { id: formId }, 371 + data: parsed, 372 + include: builderFormInclude, 373 + }); 374 + 375 + return toBuilderForm(form); 376 + } 377 + 378 + export async function setOwnedFormPublished(userId: string, formId: string, payload: unknown) { 379 + const parsed = publishFormSchema.parse(payload); 380 + const form = await assertOwner(userId, formId); 381 + 382 + if (parsed.published && form.blocks.length === 0) { 383 + throw new AppError("Add at least one block before publishing.", 422); 384 + } 385 + 386 + const updated = await db.form.update({ 387 + where: { id: formId }, 388 + data: { 389 + status: parsed.published ? FormStatus.PUBLISHED : FormStatus.DRAFT, 390 + }, 391 + include: builderFormInclude, 392 + }); 393 + 394 + return toBuilderForm(updated); 395 + } 396 + 397 + export async function addOwnedBlock(userId: string, formId: string, payload: unknown) { 398 + const parsed = createBlockSchema.parse(payload); 399 + const form = await assertOwner(userId, formId); 400 + const position = form.blocks.length; 401 + 402 + const titleByType: Record<FormBlockType, string> = { 403 + TEXT: "Section note", 404 + SHORT_TEXT: "Short text question", 405 + LONG_TEXT: "Long text question", 406 + SINGLE_CHOICE: "Single choice question", 407 + MULTIPLE_CHOICE: "Multiple choice question", 408 + }; 409 + 410 + const created = await db.formBlock.create({ 411 + data: { 412 + formId, 413 + type: parsed.type, 414 + title: titleByType[parsed.type], 415 + description: "", 416 + required: false, 417 + position, 418 + config: getDefaultBlockConfig(parsed.type), 419 + }, 420 + }); 421 + 422 + return serializeBlock(created); 423 + } 424 + 425 + export async function updateOwnedBlock(userId: string, formId: string, blockId: string, payload: unknown) { 426 + const parsed = blockUpdateSchema.parse(payload); 427 + await assertOwner(userId, formId); 428 + 429 + const block = await db.formBlock.findFirst({ 430 + where: { 431 + id: blockId, 432 + formId, 433 + form: { 434 + userId, 435 + }, 436 + }, 437 + }); 438 + 439 + if (!block) { 440 + throw new AppError("Block not found.", 404); 441 + } 442 + 443 + const config = normalizeBlockConfig(block.type, parsed.config); 444 + const updated = await db.formBlock.update({ 445 + where: { id: blockId }, 446 + data: { 447 + title: parsed.title, 448 + description: parsed.description, 449 + required: isQuestionBlock(block.type) ? parsed.required : false, 450 + config, 451 + }, 452 + }); 453 + 454 + return serializeBlock(updated); 455 + } 456 + 457 + export async function deleteOwnedBlock(userId: string, formId: string, blockId: string) { 458 + await assertOwner(userId, formId); 459 + 460 + const block = await db.formBlock.findFirst({ 461 + where: { 462 + id: blockId, 463 + formId, 464 + form: { 465 + userId, 466 + }, 467 + }, 468 + }); 469 + 470 + if (!block) { 471 + throw new AppError("Block not found.", 404); 472 + } 473 + 474 + await db.$transaction(async (tx) => { 475 + await tx.formBlock.delete({ 476 + where: { id: blockId }, 477 + }); 478 + 479 + const remainingBlocks = await tx.formBlock.findMany({ 480 + where: { formId }, 481 + orderBy: { position: "asc" }, 482 + }); 483 + 484 + await Promise.all( 485 + remainingBlocks.map((remainingBlock, index) => 486 + tx.formBlock.update({ 487 + where: { id: remainingBlock.id }, 488 + data: { position: index }, 489 + }), 490 + ), 491 + ); 492 + }); 493 + } 494 + 495 + export async function reorderOwnedBlocks(userId: string, formId: string, payload: unknown) { 496 + const parsed = reorderBlocksSchema.parse(payload); 497 + const form = await assertOwner(userId, formId); 498 + 499 + const currentIds = form.blocks.map((block) => block.id).sort(); 500 + const nextIds = [...parsed.blockIds].sort(); 501 + 502 + if (currentIds.length !== nextIds.length || currentIds.some((id, index) => id !== nextIds[index])) { 503 + throw new AppError("Block reorder payload is out of sync with the current form.", 409); 504 + } 505 + 506 + await db.$transaction( 507 + parsed.blockIds.map((blockId, index) => 508 + db.formBlock.update({ 509 + where: { id: blockId }, 510 + data: { position: index }, 511 + }), 512 + ), 513 + ); 514 + 515 + return getOwnedFormForBuilder(userId, formId); 516 + } 517 + 518 + export async function getPublicFormBySlug(slug: string) { 519 + const form = await db.form.findFirst({ 520 + where: { 521 + slug, 522 + status: FormStatus.PUBLISHED, 523 + }, 524 + include: { 525 + blocks: { 526 + orderBy: { 527 + position: "asc", 528 + }, 529 + }, 530 + }, 531 + }); 532 + 533 + if (!form) { 534 + return null; 535 + } 536 + 537 + return toPublicForm(form); 538 + } 539 + 540 + export async function createAnonymousResponse(slug: string, payload: unknown) { 541 + const answersInput = z.record(z.string(), z.union([z.string(), z.array(z.string())])).parse(payload); 542 + const form = await db.form.findFirst({ 543 + where: { 544 + slug, 545 + status: FormStatus.PUBLISHED, 546 + }, 547 + include: { 548 + blocks: { 549 + orderBy: { 550 + position: "asc", 551 + }, 552 + }, 553 + }, 554 + }); 555 + 556 + if (!form) { 557 + throw new AppError("This form is not accepting responses right now.", 404); 558 + } 559 + 560 + const blocks = form.blocks.map(serializeBlock); 561 + const normalizedAnswers = blocks.reduce<Record<string, string | string[]>>((accumulator, block) => { 562 + const answer = normalizeAnswer(block, answersInput[block.id]); 563 + 564 + if (typeof answer !== "undefined") { 565 + accumulator[block.id] = answer; 566 + } 567 + 568 + return accumulator; 569 + }, {}); 570 + 571 + const response = await db.response.create({ 572 + data: { 573 + formId: form.id, 574 + answersJson: normalizedAnswers, 575 + formSnapshotJson: blocks.map((block) => ({ 576 + id: block.id, 577 + type: block.type, 578 + title: block.title, 579 + description: block.description, 580 + required: block.required, 581 + position: block.position, 582 + config: block.config, 583 + })), 584 + }, 585 + }); 586 + 587 + return response; 588 + } 589 + 590 + export async function listResponsesForOwnedForm(userId: string, formId: string) { 591 + const form = await assertOwner(userId, formId); 592 + const responses = await db.response.findMany({ 593 + where: { 594 + formId, 595 + }, 596 + orderBy: { 597 + submittedAt: "desc", 598 + }, 599 + }); 600 + 601 + return { 602 + form, 603 + responses: responses.map((response) => ({ 604 + id: response.id, 605 + submittedAt: response.submittedAt.toISOString(), 606 + answerCount: 607 + typeof response.answersJson === "object" && response.answersJson && !Array.isArray(response.answersJson) 608 + ? Object.keys(response.answersJson as Record<string, unknown>).length 609 + : 0, 610 + })) satisfies ResponseListItem[], 611 + }; 612 + } 613 + 614 + export async function getOwnedResponseDetail(userId: string, formId: string, responseId: string): Promise<ResponseDetail> { 615 + await assertOwner(userId, formId); 616 + 617 + const response = await db.response.findFirst({ 618 + where: { 619 + id: responseId, 620 + formId, 621 + form: { 622 + userId, 623 + }, 624 + }, 625 + include: { 626 + form: { 627 + include: { 628 + blocks: { 629 + orderBy: { 630 + position: "asc", 631 + }, 632 + }, 633 + }, 634 + }, 635 + }, 636 + }); 637 + 638 + if (!response) { 639 + throw new AppError("Response not found.", 404); 640 + } 641 + 642 + return { 643 + id: response.id, 644 + submittedAt: response.submittedAt.toISOString(), 645 + answers: 646 + typeof response.answersJson === "object" && response.answersJson && !Array.isArray(response.answersJson) 647 + ? (response.answersJson as Record<string, string | string[]>) 648 + : {}, 649 + blocks: parseSnapshotBlocks(response), 650 + }; 651 + }
+28
lib/utils.ts
··· 1 + import { clsx, type ClassValue } from "clsx"; 2 + import { twMerge } from "tailwind-merge"; 3 + 4 + export function cn(...inputs: ClassValue[]) { 5 + return twMerge(clsx(inputs)); 6 + } 7 + 8 + export function slugify(value: string) { 9 + return value 10 + .toLowerCase() 11 + .trim() 12 + .replace(/[^a-z0-9]+/g, "-") 13 + .replace(/(^-|-$)+/g, "") 14 + .slice(0, 48); 15 + } 16 + 17 + export function formatDate(value: Date | string) { 18 + const date = typeof value === "string" ? new Date(value) : value; 19 + 20 + return new Intl.DateTimeFormat("en", { 21 + dateStyle: "medium", 22 + timeStyle: "short", 23 + }).format(date); 24 + } 25 + 26 + export function sentenceCase(value: string) { 27 + return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase(); 28 + }
+32
lib/validators.ts
··· 1 + import { FormBlockType } from "@prisma/client"; 2 + import { z } from "zod"; 3 + 4 + export const formMetadataSchema = z.object({ 5 + title: z.string().trim().min(1).max(120), 6 + description: z.string().trim().max(600).default(""), 7 + slug: z 8 + .string() 9 + .trim() 10 + .min(2) 11 + .max(64) 12 + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Use lowercase letters, numbers, and hyphens only."), 13 + }); 14 + 15 + export const createBlockSchema = z.object({ 16 + type: z.nativeEnum(FormBlockType), 17 + }); 18 + 19 + export const blockUpdateSchema = z.object({ 20 + title: z.string().max(160).default(""), 21 + description: z.string().max(280).default(""), 22 + required: z.boolean().default(false), 23 + config: z.unknown().default({}), 24 + }); 25 + 26 + export const reorderBlocksSchema = z.object({ 27 + blockIds: z.array(z.string()).min(1), 28 + }); 29 + 30 + export const publishFormSchema = z.object({ 31 + published: z.boolean(), 32 + });
+2
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/.openspec.yaml
··· 1 + schema: spec-driven 2 + created: 2026-04-08
+150
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/design.md
··· 1 + ## Context 2 + 3 + The repository currently contains only a starter Bun TypeScript app, so this change establishes both the initial product direction and the first real application architecture. The target product is a Typeform-inspired v0 with a polished linear runner, a simple authenticated creator experience, and anonymous public submissions. The main constraints are keeping scope intentionally narrow, shipping a credible end-to-end slice quickly, and preserving a clean foundation for later additions such as branching, richer block types, and theming. 4 + 5 + ## Goals / Non-Goals 6 + 7 + **Goals:** 8 + - Deliver an authenticated creator app for owning, editing, publishing, and reviewing forms. 9 + - Deliver a public anonymous runner that presents one block per screen in a strictly linear flow. 10 + - Support exactly five v0 block types: text, short text, long text, single choice, and multiple choice. 11 + - Keep the builder implementation simple with a left-hand ordered block list and a right-hand settings editor. 12 + - Store responses reliably without coupling submissions to user authentication. 13 + - Establish a production-ready web stack that supports rapid iteration on product UX. 14 + 15 + **Non-Goals:** 16 + - Branching logic, conditional steps, or dynamic next-step computation. 17 + - Collaboration, workspaces, or multi-editor ownership. 18 + - Deep analytics, charts, exports beyond basic response review, or integrations. 19 + - Respondent accounts, login-gated submissions, or identity-linked response history. 20 + - Full Typeform feature parity, advanced theming, embeds, uploads, or templates. 21 + 22 + ## Decisions 23 + 24 + ### 1. Use Next.js App Router as the application shell 25 + The project will move from the Bun starter entrypoint to a Next.js App Router application. This gives one framework for authenticated creator routes, public runner routes, server-rendered pages, route handlers, and server actions. 26 + 27 + **Why this decision:** 28 + - The product needs both an authenticated app surface and public shareable pages. 29 + - App Router supports colocated UI and server logic with good TypeScript ergonomics. 30 + - It keeps deployment and routing simple for an app-shaped product. 31 + 32 + **Alternatives considered:** 33 + - Keep Bun-only server code: too low-level for the product UX we need. 34 + - Split frontend and backend: cleaner separation, but unnecessary overhead for v0. 35 + 36 + ### 2. Use Auth.js only for creator authentication 37 + Authentication will exist only in creator-facing routes such as dashboard, form editing, publishing, and response review. Public form pages and submission endpoints will not require login, and submission data will not use or save creator credentials. 38 + 39 + **Why this decision:** 40 + - Matches the product requirement for anonymous public submissions. 41 + - Keeps the respondent experience frictionless. 42 + - Minimizes privacy surface area in the initial release. 43 + 44 + **Alternatives considered:** 45 + - Require respondent login: directly conflicts with product goals. 46 + - Support optional respondent auth from the start: adds complexity without v0 value. 47 + 48 + ### 3. Use Prisma with PostgreSQL for persistence 49 + The data layer will use PostgreSQL with Prisma models for users, forms, blocks, and responses. 50 + 51 + **Why this decision:** 52 + - Strong fit for an authenticated multi-user web app. 53 + - Clear schema evolution path as forms grow in complexity. 54 + - Good integration with Next.js and Auth.js. 55 + 56 + **Alternatives considered:** 57 + - SQLite: acceptable for prototypes, but weaker for a hosted multi-user product foundation. 58 + - Pure JSON/file storage: too fragile for ownership, publishing, and response querying. 59 + 60 + ### 4. Model forms as ordered blocks, not only questions 61 + Each form will contain an ordered list of blocks with stable IDs. Block types are `text`, `short_text`, `long_text`, `single_choice`, and `multiple_choice`. Text blocks are part of the runner flow but do not collect answers. 62 + 63 + **Why this decision:** 64 + - Matches the conversational runner requirement better than a question-only model. 65 + - Makes content-only screens first-class without special-case hacks. 66 + - Creates a clean path for later screen-like blocks such as welcome or thank-you variants. 67 + 68 + **Alternatives considered:** 69 + - Question-only model with separate metadata screens: less expressive and harder to extend. 70 + 71 + ### 5. Store type-specific block settings in structured JSON on each block 72 + Shared block fields will include identity, order, title, description, and required state. Type-specific details such as placeholders and option lists will live in a JSON config field. 73 + 74 + **Why this decision:** 75 + - Keeps the schema simple while block types are still evolving. 76 + - Avoids premature normalization of highly variable block settings. 77 + - Makes it easy to add more block types later without invasive migrations. 78 + 79 + **Alternatives considered:** 80 + - Separate columns per block type: rigid and noisy for a narrow v0. 81 + - Separate tables per block type: over-modeled too early. 82 + 83 + ### 6. Store submitted answers as JSON keyed by block ID 84 + Responses will be anonymous records containing timestamps and an `answersJson` payload keyed by block ID. Question blocks will map to string or string-array values depending on block type; text blocks will have no answer entry. 85 + 86 + **Why this decision:** 87 + - Fits the small fixed set of v0 block types. 88 + - Supports response rendering without complex joins or polymorphic answer tables. 89 + - Preserves flexibility while the product shape is still stabilizing. 90 + 91 + **Alternatives considered:** 92 + - Fully normalized answer tables: more queryable, but adds complexity before it is needed. 93 + 94 + ### 7. Builder UX will use a master-detail layout 95 + The builder will use a left panel for ordered block management and a right panel for editing the selected block’s settings. Reordering will use dnd-kit. The builder is optimized for correctness and speed over maximal visual editing. 96 + 97 + **Why this decision:** 98 + - Chosen explicitly to keep v0 simpler and more reliable. 99 + - Reduces state-management complexity compared with inline canvas editing. 100 + - Works well with a narrow set of block types. 101 + 102 + **Alternatives considered:** 103 + - Canvas-first inline editing: more visually rich, but significantly higher implementation complexity. 104 + 105 + ### 8. Runner UX will prioritize one-block focus with motion and simple validation 106 + The public form runner will render exactly one block at a time with next/back navigation, a progress indicator, lightweight transitions, and per-step required validation. Progress will count all blocks in the flow, including text blocks, to preserve a predictable step count. 107 + 108 + **Why this decision:** 109 + - Produces the intended Typeform-like feel. 110 + - Keeps the flow model straightforward because all steps are linear and visible. 111 + - Avoids confusion caused by hidden non-question screens. 112 + 113 + **Alternatives considered:** 114 + - Render only answerable blocks in progress: slightly more compact, but less faithful to the actual flow. 115 + 116 + ### 9. Allow form editing after responses exist while keeping block IDs stable 117 + Creators may continue editing forms after responses have been collected. Block IDs must remain stable so existing responses can still be mapped back to the block structure they were submitted against. 118 + 119 + **Why this decision:** 120 + - Supports a practical creator workflow without requiring full versioning in v0. 121 + - Minimizes data loss risk while keeping implementation scope contained. 122 + 123 + **Alternatives considered:** 124 + - Lock forms after first response: too restrictive for real usage. 125 + - Full form versioning: valuable later, but too heavy for v0. 126 + 127 + ## Risks / Trade-offs 128 + 129 + - **Editing forms after responses may create mismatches with historic answers** → Keep stable block IDs and present response data defensively using stored labels/config where needed in later iterations. 130 + - **JSON-based block config and answers trade queryability for speed** → Accept this for v0 and revisit normalization when analytics or filtering requirements appear. 131 + - **Next.js migration replaces the current starter shape completely** → Treat the current repo as scaffolding only and build the new app structure deliberately from scratch. 132 + - **Anonymous public submissions can invite spam or abuse** → Keep the initial submission surface minimal and add rate limiting or bot protection later if real usage demands it. 133 + - **Polished runner interactions can consume disproportionate frontend time** → Limit v0 motion to a few high-value transitions and keep the builder intentionally plain. 134 + 135 + ## Migration Plan 136 + 137 + 1. Replace the Bun starter entrypoint with a Next.js application structure. 138 + 2. Add dependencies for Tailwind, shadcn/ui, Framer Motion, dnd-kit, Prisma, PostgreSQL, and Auth.js. 139 + 3. Introduce Prisma schema and initial migrations for users, forms, blocks, and responses. 140 + 4. Build creator authentication and protected app routes. 141 + 5. Build the dashboard, builder, publish flow, public runner, and response review pages. 142 + 6. Validate end-to-end flows locally with draft and published forms. 143 + 144 + Rollback is straightforward at this stage because there is no existing production system or user data to preserve. 145 + 146 + ## Open Questions 147 + 148 + - Which Auth.js provider strategy should be used first for creator sign-in: credentials, OAuth, or both? 149 + - Should v0 include draft autosave in the builder, or are explicit saves sufficient for the first cut? 150 + - Should response metadata include IP/user-agent fields for abuse investigation, or should v0 avoid storing them entirely?
+31
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/proposal.md
··· 1 + ## Why 2 + 3 + This project currently has no product direction beyond a starter Bun app. We want to establish a focused, buildable v0 for a Typeform-inspired product that emphasizes a polished one-screen-at-a-time form experience instead of broad feature parity. 4 + 5 + ## What Changes 6 + 7 + - Introduce authenticated creator accounts for owning and managing forms. 8 + - Add a form builder for creating linear conversational forms with a simple sidebar-and-editor workflow. 9 + - Support five v0 block types: text, short text, long text, single choice, and multiple choice. 10 + - Add publish/unpublish and public shareable form links. 11 + - Add a public form runner that collects anonymous responses without requiring respondent authentication. 12 + - Add creator response views for listing and inspecting submitted responses. 13 + - Establish the initial technical foundation with Next.js, TypeScript, Tailwind CSS, shadcn/ui, Framer Motion, dnd-kit, Prisma, PostgreSQL, and Auth.js. 14 + 15 + ## Capabilities 16 + 17 + ### New Capabilities 18 + - `creator-auth`: authenticate creators so they can create, edit, publish, and review only their own forms. 19 + - `conversational-form-builder`: create and manage linear forms with ordered blocks, block editing, and publish/share controls. 20 + - `anonymous-form-runner`: present published forms as one-block-at-a-time conversational flows and accept anonymous public submissions. 21 + - `response-review`: let creators list and inspect responses submitted to forms they own. 22 + 23 + ### Modified Capabilities 24 + - None. 25 + 26 + ## Impact 27 + 28 + - Replaces the current starter-only direction with a real web product architecture. 29 + - Introduces application routes for creator dashboards, form editing, public form access, and response review. 30 + - Adds authentication, database models, form/block/response persistence, and public submission endpoints. 31 + - Adds new frontend dependencies for app UI, motion, drag-and-drop, ORM, database access, and auth.
+48
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/specs/anonymous-form-runner/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Published forms are publicly accessible without respondent authentication 4 + The system SHALL allow any respondent to open a published form's public route without signing in. Draft or unpublished forms SHALL NOT be available for public response collection. 5 + 6 + #### Scenario: Respondent opens a published form 7 + - **WHEN** a respondent navigates to the public route of a published form 8 + - **THEN** the system displays the form runner without requiring authentication 9 + 10 + #### Scenario: Respondent opens an unpublished form 11 + - **WHEN** a respondent navigates to the public route of a draft or unpublished form 12 + - **THEN** the system does not allow public response collection for that form 13 + 14 + ### Requirement: Runner presents blocks in a linear one-at-a-time flow 15 + The system SHALL present form blocks in saved order and display exactly one block at a time with forward and backward navigation. Text blocks SHALL appear as part of the flow but SHALL NOT collect an answer. 16 + 17 + #### Scenario: Respondent moves through the form 18 + - **WHEN** a respondent advances through a published form 19 + - **THEN** the system shows each block one at a time in the form's saved order 20 + 21 + #### Scenario: Respondent returns to a previous block 22 + - **WHEN** a respondent navigates backward in the runner 23 + - **THEN** the system returns them to the previous block without breaking the saved in-progress answers for the session 24 + 25 + ### Requirement: Runner shows progress across the full flow 26 + The system SHALL display progress for the respondent based on the linear block sequence, including non-answerable text blocks. 27 + 28 + #### Scenario: Respondent views a text block in the flow 29 + - **WHEN** a respondent is on a text block step 30 + - **THEN** the system includes that step in the visible progress through the form 31 + 32 + ### Requirement: Required question blocks are validated before advancement 33 + The system SHALL prevent a respondent from advancing past a required question block until a valid answer is provided for that block type. 34 + 35 + #### Scenario: Respondent skips a required short text question 36 + - **WHEN** a respondent attempts to continue from a required text question without an answer 37 + - **THEN** the system blocks advancement and indicates that an answer is required 38 + 39 + #### Scenario: Respondent skips a required multiple choice question 40 + - **WHEN** a respondent attempts to continue from a required multiple choice block without selecting any options 41 + - **THEN** the system blocks advancement and indicates that an answer is required 42 + 43 + ### Requirement: Form submission stores anonymous responses 44 + The system SHALL allow a respondent to submit a completed published form anonymously and SHALL store only the form response data needed for review, without using creator login credentials as part of submission identity. 45 + 46 + #### Scenario: Respondent submits a completed form 47 + - **WHEN** a respondent completes the final step of a published form and submits it 48 + - **THEN** the system stores an anonymous response for that form and shows a completion state
+60
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/specs/conversational-form-builder/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Creator can create and manage form metadata 4 + The system SHALL allow an authenticated creator to create a form draft and edit its title and shareable identity needed for public publishing. 5 + 6 + #### Scenario: Creator creates a new form 7 + - **WHEN** an authenticated creator creates a form 8 + - **THEN** the system creates a new draft form owned by that creator 9 + 10 + #### Scenario: Creator updates form title 11 + - **WHEN** an authenticated creator edits the title of an owned form 12 + - **THEN** the system saves the updated title for that form 13 + 14 + ### Requirement: Creator can manage an ordered list of supported blocks 15 + The system SHALL allow an authenticated creator to add, select, edit, reorder, and remove blocks in a form using only the supported v0 block types: text, short text, long text, single choice, and multiple choice. 16 + 17 + #### Scenario: Creator adds a block 18 + - **WHEN** an authenticated creator adds a supported block type to an owned form 19 + - **THEN** the system appends the new block to the form with a stable block identifier 20 + 21 + #### Scenario: Creator reorders blocks 22 + - **WHEN** an authenticated creator changes the order of blocks in an owned form 23 + - **THEN** the system saves the new block order for the form 24 + 25 + #### Scenario: Creator removes a block 26 + - **WHEN** an authenticated creator removes a block from an owned form 27 + - **THEN** the system removes that block from the current form structure 28 + 29 + ### Requirement: Builder exposes block editing through a master-detail layout 30 + The system SHALL present a builder interface with an ordered block list for navigation and reordering and a separate editor panel for the selected block's settings. 31 + 32 + #### Scenario: Creator selects a block 33 + - **WHEN** an authenticated creator selects a block in the block list 34 + - **THEN** the system displays that block's editable settings in the editor panel 35 + 36 + ### Requirement: Question blocks support required state and type-specific configuration 37 + The system SHALL allow question blocks to be marked as required and SHALL support type-specific configuration for placeholders and choice options. Text blocks SHALL NOT be answerable and SHALL NOT expose a required setting. 38 + 39 + #### Scenario: Creator marks a short text question required 40 + - **WHEN** an authenticated creator enables required state for a question block 41 + - **THEN** the system saves that question block as required 42 + 43 + #### Scenario: Creator edits single choice options 44 + - **WHEN** an authenticated creator updates the options for a single choice or multiple choice block 45 + - **THEN** the system saves the updated option list for that block 46 + 47 + #### Scenario: Creator edits a text block 48 + - **WHEN** an authenticated creator edits a text block 49 + - **THEN** the system allows content updates without exposing answer validation settings 50 + 51 + ### Requirement: Creator can publish and unpublish forms 52 + The system SHALL allow an authenticated creator to publish or unpublish an owned form and SHALL expose a public shareable route only for published forms. 53 + 54 + #### Scenario: Creator publishes a form 55 + - **WHEN** an authenticated creator publishes an owned form 56 + - **THEN** the system marks the form as published and makes its public route available for respondents 57 + 58 + #### Scenario: Creator unpublishes a form 59 + - **WHEN** an authenticated creator unpublishes an owned form 60 + - **THEN** the system marks the form as unavailable for new public submissions
+23
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/specs/creator-auth/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Creator authentication gates creator features 4 + The system SHALL require authentication before a user can access creator-only features, including creating forms, editing owned forms, publishing forms, and reviewing responses. 5 + 6 + #### Scenario: Unauthenticated user opens creator dashboard 7 + - **WHEN** an unauthenticated user navigates to a creator-only route 8 + - **THEN** the system redirects them to sign in before showing creator functionality 9 + 10 + #### Scenario: Authenticated creator opens creator dashboard 11 + - **WHEN** an authenticated creator navigates to a creator-only route 12 + - **THEN** the system grants access to creator functionality associated with their account 13 + 14 + ### Requirement: Creator ownership is enforced for managed forms 15 + The system SHALL allow creators to manage only forms they own and SHALL prevent access to editing, publishing, or response review for forms owned by other creators. 16 + 17 + #### Scenario: Creator opens an owned form 18 + - **WHEN** an authenticated creator requests a form they own 19 + - **THEN** the system allows them to edit and manage that form 20 + 21 + #### Scenario: Creator opens another creator's form 22 + - **WHEN** an authenticated creator requests a form they do not own 23 + - **THEN** the system denies access to form management and response review for that form
+23
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/specs/response-review/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Creator can list responses for owned forms 4 + The system SHALL allow an authenticated creator to view submitted responses for forms they own and SHALL prevent them from listing responses for forms owned by others. 5 + 6 + #### Scenario: Creator opens responses for an owned form 7 + - **WHEN** an authenticated creator opens the responses view for a form they own 8 + - **THEN** the system displays the submitted responses for that form 9 + 10 + #### Scenario: Creator opens responses for another creator's form 11 + - **WHEN** an authenticated creator opens the responses view for a form they do not own 12 + - **THEN** the system denies access to that form's submitted responses 13 + 14 + ### Requirement: Creator can inspect a single response in form order 15 + The system SHALL allow an authenticated creator to open an individual response and inspect answers mapped to the corresponding form blocks in the saved block order. 16 + 17 + #### Scenario: Creator opens one response 18 + - **WHEN** an authenticated creator selects a submitted response for a form they own 19 + - **THEN** the system displays that response's answers in the order of the form's blocks 20 + 21 + #### Scenario: Creator inspects response with text blocks in the form 22 + - **WHEN** an authenticated creator views a response for a form that includes text blocks 23 + - **THEN** the system shows only answerable block responses while preserving the form order context for the submission
+43
openspec/changes/archive/2026-04-08-build-typeform-style-forms-v0/tasks.md
··· 1 + ## 1. Project foundation 2 + 3 + - [x] 1.1 Replace the Bun starter app with a Next.js App Router project structure and baseline app shell 4 + - [x] 1.2 Add and configure the core dependencies: Tailwind CSS, shadcn/ui, Framer Motion, dnd-kit, Prisma, PostgreSQL, and Auth.js 5 + - [x] 1.3 Set up shared application utilities for database access, auth configuration, and route protection 6 + 7 + ## 2. Data model and persistence 8 + 9 + - [x] 2.1 Define Prisma models and migrations for users, forms, form blocks, and anonymous responses 10 + - [x] 2.2 Implement server-side data access for creating, updating, ordering, publishing, and loading owned forms 11 + - [x] 2.3 Implement server-side response persistence for anonymous submissions and creator-side response retrieval 12 + 13 + ## 3. Creator authentication and ownership 14 + 15 + - [x] 3.1 Implement creator sign-in and session handling with Auth.js 16 + - [x] 3.2 Protect creator-only routes and redirect unauthenticated users to sign in 17 + - [x] 3.3 Enforce form ownership checks for editing, publishing, and response review actions 18 + 19 + ## 4. Creator dashboard and builder 20 + 21 + - [x] 4.1 Build the creator dashboard for listing owned forms and creating a new draft form 22 + - [x] 4.2 Build the form builder master-detail layout with a block list and selected-block editor panel 23 + - [x] 4.3 Implement block creation, selection, editing, deletion, and drag-and-drop reordering for the five v0 block types 24 + - [x] 4.4 Implement form metadata editing plus publish/unpublish and share-link controls 25 + 26 + ## 5. Public conversational runner 27 + 28 + - [x] 5.1 Build the public published-form route and reject draft or unpublished forms from public response collection 29 + - [x] 5.2 Implement the one-block-at-a-time runner with next/back navigation, progress display, and motion transitions 30 + - [x] 5.3 Implement block-specific inputs and required validation for short text, long text, single choice, and multiple choice blocks 31 + - [x] 5.4 Implement anonymous submission and completion state for the final runner step 32 + 33 + ## 6. Response review 34 + 35 + - [x] 6.1 Build the creator responses list view for an owned form 36 + - [x] 6.2 Build the single-response detail view with answers shown in form order 37 + - [x] 6.3 Ensure text blocks are handled correctly in response presentation while preserving flow context 38 + 39 + ## 7. Integration polish 40 + 41 + - [x] 7.1 Connect end-to-end flows across dashboard, builder, publishing, public runner, and response review 42 + - [x] 7.2 Add basic empty, loading, and error states for key creator and respondent surfaces 43 + - [x] 7.3 Validate the v0 experience manually for anonymous submissions, ownership enforcement, and linear runner behavior
+20
openspec/config.yaml
··· 1 + schema: spec-driven 2 + 3 + # Project context (optional) 4 + # This is shown to AI when creating artifacts. 5 + # Add your tech stack, conventions, style guides, domain knowledge, etc. 6 + # Example: 7 + # context: | 8 + # Tech stack: TypeScript, React, Node.js 9 + # We use conventional commits 10 + # Domain: e-commerce platform 11 + 12 + # Per-artifact rules (optional) 13 + # Add custom rules for specific artifacts. 14 + # Example: 15 + # rules: 16 + # proposal: 17 + # - Keep proposals under 500 words 18 + # - Always include a "Non-goals" section 19 + # tasks: 20 + # - Break tasks into chunks of max 2 hours
+48
openspec/specs/anonymous-form-runner/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Published forms are publicly accessible without respondent authentication 4 + The system SHALL allow any respondent to open a published form's public route without signing in. Draft or unpublished forms SHALL NOT be available for public response collection. 5 + 6 + #### Scenario: Respondent opens a published form 7 + - **WHEN** a respondent navigates to the public route of a published form 8 + - **THEN** the system displays the form runner without requiring authentication 9 + 10 + #### Scenario: Respondent opens an unpublished form 11 + - **WHEN** a respondent navigates to the public route of a draft or unpublished form 12 + - **THEN** the system does not allow public response collection for that form 13 + 14 + ### Requirement: Runner presents blocks in a linear one-at-a-time flow 15 + The system SHALL present form blocks in saved order and display exactly one block at a time with forward and backward navigation. Text blocks SHALL appear as part of the flow but SHALL NOT collect an answer. 16 + 17 + #### Scenario: Respondent moves through the form 18 + - **WHEN** a respondent advances through a published form 19 + - **THEN** the system shows each block one at a time in the form's saved order 20 + 21 + #### Scenario: Respondent returns to a previous block 22 + - **WHEN** a respondent navigates backward in the runner 23 + - **THEN** the system returns them to the previous block without breaking the saved in-progress answers for the session 24 + 25 + ### Requirement: Runner shows progress across the full flow 26 + The system SHALL display progress for the respondent based on the linear block sequence, including non-answerable text blocks. 27 + 28 + #### Scenario: Respondent views a text block in the flow 29 + - **WHEN** a respondent is on a text block step 30 + - **THEN** the system includes that step in the visible progress through the form 31 + 32 + ### Requirement: Required question blocks are validated before advancement 33 + The system SHALL prevent a respondent from advancing past a required question block until a valid answer is provided for that block type. 34 + 35 + #### Scenario: Respondent skips a required short text question 36 + - **WHEN** a respondent attempts to continue from a required text question without an answer 37 + - **THEN** the system blocks advancement and indicates that an answer is required 38 + 39 + #### Scenario: Respondent skips a required multiple choice question 40 + - **WHEN** a respondent attempts to continue from a required multiple choice block without selecting any options 41 + - **THEN** the system blocks advancement and indicates that an answer is required 42 + 43 + ### Requirement: Form submission stores anonymous responses 44 + The system SHALL allow a respondent to submit a completed published form anonymously and SHALL store only the form response data needed for review, without using creator login credentials as part of submission identity. 45 + 46 + #### Scenario: Respondent submits a completed form 47 + - **WHEN** a respondent completes the final step of a published form and submits it 48 + - **THEN** the system stores an anonymous response for that form and shows a completion state
+60
openspec/specs/conversational-form-builder/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Creator can create and manage form metadata 4 + The system SHALL allow an authenticated creator to create a form draft and edit its title and shareable identity needed for public publishing. 5 + 6 + #### Scenario: Creator creates a new form 7 + - **WHEN** an authenticated creator creates a form 8 + - **THEN** the system creates a new draft form owned by that creator 9 + 10 + #### Scenario: Creator updates form title 11 + - **WHEN** an authenticated creator edits the title of an owned form 12 + - **THEN** the system saves the updated title for that form 13 + 14 + ### Requirement: Creator can manage an ordered list of supported blocks 15 + The system SHALL allow an authenticated creator to add, select, edit, reorder, and remove blocks in a form using only the supported v0 block types: text, short text, long text, single choice, and multiple choice. 16 + 17 + #### Scenario: Creator adds a block 18 + - **WHEN** an authenticated creator adds a supported block type to an owned form 19 + - **THEN** the system appends the new block to the form with a stable block identifier 20 + 21 + #### Scenario: Creator reorders blocks 22 + - **WHEN** an authenticated creator changes the order of blocks in an owned form 23 + - **THEN** the system saves the new block order for the form 24 + 25 + #### Scenario: Creator removes a block 26 + - **WHEN** an authenticated creator removes a block from an owned form 27 + - **THEN** the system removes that block from the current form structure 28 + 29 + ### Requirement: Builder exposes block editing through a master-detail layout 30 + The system SHALL present a builder interface with an ordered block list for navigation and reordering and a separate editor panel for the selected block's settings. 31 + 32 + #### Scenario: Creator selects a block 33 + - **WHEN** an authenticated creator selects a block in the block list 34 + - **THEN** the system displays that block's editable settings in the editor panel 35 + 36 + ### Requirement: Question blocks support required state and type-specific configuration 37 + The system SHALL allow question blocks to be marked as required and SHALL support type-specific configuration for placeholders and choice options. Text blocks SHALL NOT be answerable and SHALL NOT expose a required setting. 38 + 39 + #### Scenario: Creator marks a short text question required 40 + - **WHEN** an authenticated creator enables required state for a question block 41 + - **THEN** the system saves that question block as required 42 + 43 + #### Scenario: Creator edits single choice options 44 + - **WHEN** an authenticated creator updates the options for a single choice or multiple choice block 45 + - **THEN** the system saves the updated option list for that block 46 + 47 + #### Scenario: Creator edits a text block 48 + - **WHEN** an authenticated creator edits a text block 49 + - **THEN** the system allows content updates without exposing answer validation settings 50 + 51 + ### Requirement: Creator can publish and unpublish forms 52 + The system SHALL allow an authenticated creator to publish or unpublish an owned form and SHALL expose a public shareable route only for published forms. 53 + 54 + #### Scenario: Creator publishes a form 55 + - **WHEN** an authenticated creator publishes an owned form 56 + - **THEN** the system marks the form as published and makes its public route available for respondents 57 + 58 + #### Scenario: Creator unpublishes a form 59 + - **WHEN** an authenticated creator unpublishes an owned form 60 + - **THEN** the system marks the form as unavailable for new public submissions
+23
openspec/specs/creator-auth/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Creator authentication gates creator features 4 + The system SHALL require authentication before a user can access creator-only features, including creating forms, editing owned forms, publishing forms, and reviewing responses. 5 + 6 + #### Scenario: Unauthenticated user opens creator dashboard 7 + - **WHEN** an unauthenticated user navigates to a creator-only route 8 + - **THEN** the system redirects them to sign in before showing creator functionality 9 + 10 + #### Scenario: Authenticated creator opens creator dashboard 11 + - **WHEN** an authenticated creator navigates to a creator-only route 12 + - **THEN** the system grants access to creator functionality associated with their account 13 + 14 + ### Requirement: Creator ownership is enforced for managed forms 15 + The system SHALL allow creators to manage only forms they own and SHALL prevent access to editing, publishing, or response review for forms owned by other creators. 16 + 17 + #### Scenario: Creator opens an owned form 18 + - **WHEN** an authenticated creator requests a form they own 19 + - **THEN** the system allows them to edit and manage that form 20 + 21 + #### Scenario: Creator opens another creator's form 22 + - **WHEN** an authenticated creator requests a form they do not own 23 + - **THEN** the system denies access to form management and response review for that form
+23
openspec/specs/response-review/spec.md
··· 1 + ## ADDED Requirements 2 + 3 + ### Requirement: Creator can list responses for owned forms 4 + The system SHALL allow an authenticated creator to view submitted responses for forms they own and SHALL prevent them from listing responses for forms owned by others. 5 + 6 + #### Scenario: Creator opens responses for an owned form 7 + - **WHEN** an authenticated creator opens the responses view for a form they own 8 + - **THEN** the system displays the submitted responses for that form 9 + 10 + #### Scenario: Creator opens responses for another creator's form 11 + - **WHEN** an authenticated creator opens the responses view for a form they do not own 12 + - **THEN** the system denies access to that form's submitted responses 13 + 14 + ### Requirement: Creator can inspect a single response in form order 15 + The system SHALL allow an authenticated creator to open an individual response and inspect answers mapped to the corresponding form blocks in the saved block order. 16 + 17 + #### Scenario: Creator opens one response 18 + - **WHEN** an authenticated creator selects a submitted response for a form they own 19 + - **THEN** the system displays that response's answers in the order of the form's blocks 20 + 21 + #### Scenario: Creator inspects response with text blocks in the form 22 + - **WHEN** an authenticated creator views a response for a form that includes text blocks 23 + - **THEN** the system shows only answerable block responses while preserving the form order context for the submission
+47
package.json
··· 1 + { 2 + "name": "the-forms", 3 + "version": "0.1.0", 4 + "private": true, 5 + "scripts": { 6 + "dev": "next dev", 7 + "build": "next build", 8 + "start": "next start", 9 + "lint": "eslint", 10 + "db:up": "podman-compose up -d", 11 + "db:down": "podman-compose down", 12 + "db:logs": "podman-compose logs -f postgres", 13 + "db:reset": "podman-compose down -v && podman-compose up -d", 14 + "prisma:generate": "prisma generate", 15 + "prisma:migrate": "prisma migrate dev", 16 + "prisma:studio": "prisma studio", 17 + "postinstall": "prisma generate" 18 + }, 19 + "devDependencies": { 20 + "@tailwindcss/postcss": "^4.2.2", 21 + "@types/node": "^25.5.2", 22 + "@types/react": "^19.2.14", 23 + "@types/react-dom": "^19.2.3", 24 + "eslint": "9.39.1", 25 + "eslint-config-next": "^16.2.2", 26 + "prisma": "6", 27 + "tailwindcss": "^4.2.2", 28 + "typescript": "^6.0.2" 29 + }, 30 + "dependencies": { 31 + "@auth/prisma-adapter": "^2.11.1", 32 + "@dnd-kit/core": "^6.3.1", 33 + "@dnd-kit/sortable": "^10.0.0", 34 + "@dnd-kit/utilities": "^3.2.2", 35 + "@prisma/client": "6", 36 + "class-variance-authority": "^0.7.1", 37 + "clsx": "^2.1.1", 38 + "framer-motion": "^12.38.0", 39 + "lucide-react": "^1.7.0", 40 + "next": "^16.2.2", 41 + "next-auth": "^4.24.13", 42 + "react": "^19.2.4", 43 + "react-dom": "^19.2.4", 44 + "tailwind-merge": "^3.5.0", 45 + "zod": "^4.3.6" 46 + } 47 + }
+7
postcss.config.mjs
··· 1 + const config = { 2 + plugins: { 3 + "@tailwindcss/postcss": {}, 4 + }, 5 + }; 6 + 7 + export default config;
+143
prisma/migrations/20260408140126_init/migration.sql
··· 1 + -- CreateEnum 2 + CREATE TYPE "FormStatus" AS ENUM ('DRAFT', 'PUBLISHED'); 3 + 4 + -- CreateEnum 5 + CREATE TYPE "FormBlockType" AS ENUM ('TEXT', 'SHORT_TEXT', 'LONG_TEXT', 'SINGLE_CHOICE', 'MULTIPLE_CHOICE'); 6 + 7 + -- CreateTable 8 + CREATE TABLE "User" ( 9 + "id" TEXT NOT NULL, 10 + "name" TEXT, 11 + "email" TEXT, 12 + "emailVerified" TIMESTAMP(3), 13 + "image" TEXT, 14 + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 15 + "updatedAt" TIMESTAMP(3) NOT NULL, 16 + 17 + CONSTRAINT "User_pkey" PRIMARY KEY ("id") 18 + ); 19 + 20 + -- CreateTable 21 + CREATE TABLE "Account" ( 22 + "id" TEXT NOT NULL, 23 + "userId" TEXT NOT NULL, 24 + "type" TEXT NOT NULL, 25 + "provider" TEXT NOT NULL, 26 + "providerAccountId" TEXT NOT NULL, 27 + "refresh_token" TEXT, 28 + "access_token" TEXT, 29 + "expires_at" INTEGER, 30 + "token_type" TEXT, 31 + "scope" TEXT, 32 + "id_token" TEXT, 33 + "session_state" TEXT, 34 + "refresh_token_expires_in" INTEGER, 35 + 36 + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") 37 + ); 38 + 39 + -- CreateTable 40 + CREATE TABLE "Session" ( 41 + "id" TEXT NOT NULL, 42 + "sessionToken" TEXT NOT NULL, 43 + "userId" TEXT NOT NULL, 44 + "expires" TIMESTAMP(3) NOT NULL, 45 + 46 + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") 47 + ); 48 + 49 + -- CreateTable 50 + CREATE TABLE "VerificationToken" ( 51 + "identifier" TEXT NOT NULL, 52 + "token" TEXT NOT NULL, 53 + "expires" TIMESTAMP(3) NOT NULL 54 + ); 55 + 56 + -- CreateTable 57 + CREATE TABLE "Form" ( 58 + "id" TEXT NOT NULL, 59 + "userId" TEXT NOT NULL, 60 + "title" TEXT NOT NULL DEFAULT 'Untitled form', 61 + "description" TEXT NOT NULL DEFAULT '', 62 + "slug" TEXT NOT NULL, 63 + "status" "FormStatus" NOT NULL DEFAULT 'DRAFT', 64 + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 65 + "updatedAt" TIMESTAMP(3) NOT NULL, 66 + 67 + CONSTRAINT "Form_pkey" PRIMARY KEY ("id") 68 + ); 69 + 70 + -- CreateTable 71 + CREATE TABLE "FormBlock" ( 72 + "id" TEXT NOT NULL, 73 + "formId" TEXT NOT NULL, 74 + "type" "FormBlockType" NOT NULL, 75 + "title" TEXT NOT NULL DEFAULT '', 76 + "description" TEXT NOT NULL DEFAULT '', 77 + "required" BOOLEAN NOT NULL DEFAULT false, 78 + "position" INTEGER NOT NULL, 79 + "config" JSONB NOT NULL, 80 + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 81 + "updatedAt" TIMESTAMP(3) NOT NULL, 82 + 83 + CONSTRAINT "FormBlock_pkey" PRIMARY KEY ("id") 84 + ); 85 + 86 + -- CreateTable 87 + CREATE TABLE "Response" ( 88 + "id" TEXT NOT NULL, 89 + "formId" TEXT NOT NULL, 90 + "answersJson" JSONB NOT NULL, 91 + "formSnapshotJson" JSONB NOT NULL, 92 + "submittedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 93 + 94 + CONSTRAINT "Response_pkey" PRIMARY KEY ("id") 95 + ); 96 + 97 + -- CreateIndex 98 + CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 99 + 100 + -- CreateIndex 101 + CREATE INDEX "Account_userId_idx" ON "Account"("userId"); 102 + 103 + -- CreateIndex 104 + CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); 105 + 106 + -- CreateIndex 107 + CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); 108 + 109 + -- CreateIndex 110 + CREATE INDEX "Session_userId_idx" ON "Session"("userId"); 111 + 112 + -- CreateIndex 113 + CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); 114 + 115 + -- CreateIndex 116 + CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); 117 + 118 + -- CreateIndex 119 + CREATE UNIQUE INDEX "Form_slug_key" ON "Form"("slug"); 120 + 121 + -- CreateIndex 122 + CREATE INDEX "Form_userId_updatedAt_idx" ON "Form"("userId", "updatedAt" DESC); 123 + 124 + -- CreateIndex 125 + CREATE INDEX "FormBlock_formId_position_idx" ON "FormBlock"("formId", "position" ASC); 126 + 127 + -- CreateIndex 128 + CREATE INDEX "Response_formId_submittedAt_idx" ON "Response"("formId", "submittedAt" DESC); 129 + 130 + -- AddForeignKey 131 + ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 132 + 133 + -- AddForeignKey 134 + ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 135 + 136 + -- AddForeignKey 137 + ALTER TABLE "Form" ADD CONSTRAINT "Form_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 138 + 139 + -- AddForeignKey 140 + ALTER TABLE "FormBlock" ADD CONSTRAINT "FormBlock_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE ON UPDATE CASCADE; 141 + 142 + -- AddForeignKey 143 + ALTER TABLE "Response" ADD CONSTRAINT "Response_formId_fkey" FOREIGN KEY ("formId") REFERENCES "Form"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+3
prisma/migrations/migration_lock.toml
··· 1 + # Please do not edit this file manually 2 + # It should be added in your version-control system (e.g., Git) 3 + provider = "postgresql"
+115
prisma/schema.prisma
··· 1 + generator client { 2 + provider = "prisma-client-js" 3 + } 4 + 5 + datasource db { 6 + provider = "postgresql" 7 + url = env("DATABASE_URL") 8 + } 9 + 10 + enum FormStatus { 11 + DRAFT 12 + PUBLISHED 13 + } 14 + 15 + enum FormBlockType { 16 + TEXT 17 + SHORT_TEXT 18 + LONG_TEXT 19 + SINGLE_CHOICE 20 + MULTIPLE_CHOICE 21 + } 22 + 23 + model User { 24 + id String @id @default(cuid()) 25 + name String? 26 + email String? @unique 27 + emailVerified DateTime? 28 + image String? 29 + accounts Account[] 30 + sessions Session[] 31 + forms Form[] 32 + createdAt DateTime @default(now()) 33 + updatedAt DateTime @updatedAt 34 + } 35 + 36 + model Account { 37 + id String @id @default(cuid()) 38 + userId String 39 + type String 40 + provider String 41 + providerAccountId String 42 + refresh_token String? @db.Text 43 + access_token String? @db.Text 44 + expires_at Int? 45 + token_type String? 46 + scope String? 47 + id_token String? @db.Text 48 + session_state String? 49 + refresh_token_expires_in Int? 50 + user User @relation(fields: [userId], references: [id], onDelete: Cascade) 51 + 52 + @@unique([provider, providerAccountId]) 53 + @@index([userId]) 54 + } 55 + 56 + model Session { 57 + id String @id @default(cuid()) 58 + sessionToken String @unique 59 + userId String 60 + expires DateTime 61 + user User @relation(fields: [userId], references: [id], onDelete: Cascade) 62 + 63 + @@index([userId]) 64 + } 65 + 66 + model VerificationToken { 67 + identifier String 68 + token String @unique 69 + expires DateTime 70 + 71 + @@unique([identifier, token]) 72 + } 73 + 74 + model Form { 75 + id String @id @default(cuid()) 76 + userId String 77 + title String @default("Untitled form") 78 + description String @default("") 79 + slug String @unique 80 + status FormStatus @default(DRAFT) 81 + user User @relation(fields: [userId], references: [id], onDelete: Cascade) 82 + blocks FormBlock[] 83 + responses Response[] 84 + createdAt DateTime @default(now()) 85 + updatedAt DateTime @updatedAt 86 + 87 + @@index([userId, updatedAt(sort: Desc)]) 88 + } 89 + 90 + model FormBlock { 91 + id String @id @default(cuid()) 92 + formId String 93 + type FormBlockType 94 + title String @default("") 95 + description String @default("") 96 + required Boolean @default(false) 97 + position Int 98 + config Json 99 + form Form @relation(fields: [formId], references: [id], onDelete: Cascade) 100 + createdAt DateTime @default(now()) 101 + updatedAt DateTime @updatedAt 102 + 103 + @@index([formId, position(sort: Asc)]) 104 + } 105 + 106 + model Response { 107 + id String @id @default(cuid()) 108 + formId String 109 + answersJson Json 110 + formSnapshotJson Json 111 + submittedAt DateTime @default(now()) 112 + form Form @relation(fields: [formId], references: [id], onDelete: Cascade) 113 + 114 + @@index([formId, submittedAt(sort: Desc)]) 115 + }
+1
public/file.svg
··· 1 + <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
+1
public/globe.svg
··· 1 + <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+1
public/next.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
+1
public/vercel.svg
··· 1 + <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
+1
public/window.svg
··· 1 + <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
+34
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2017", 4 + "lib": ["dom", "dom.iterable", "esnext"], 5 + "allowJs": true, 6 + "skipLibCheck": true, 7 + "strict": true, 8 + "noEmit": true, 9 + "esModuleInterop": true, 10 + "module": "esnext", 11 + "moduleResolution": "bundler", 12 + "resolveJsonModule": true, 13 + "isolatedModules": true, 14 + "jsx": "react-jsx", 15 + "incremental": true, 16 + "plugins": [ 17 + { 18 + "name": "next" 19 + } 20 + ], 21 + "paths": { 22 + "@/*": ["./*"] 23 + } 24 + }, 25 + "include": [ 26 + "next-env.d.ts", 27 + "**/*.ts", 28 + "**/*.tsx", 29 + ".next/types/**/*.ts", 30 + ".next/dev/types/**/*.ts", 31 + "**/*.mts" 32 + ], 33 + "exclude": ["node_modules"] 34 + }
+13
types/next-auth.d.ts
··· 1 + import { DefaultSession } from "next-auth"; 2 + 3 + declare module "next-auth" { 4 + interface Session { 5 + user: DefaultSession["user"] & { 6 + id: string; 7 + }; 8 + } 9 + 10 + interface User { 11 + id: string; 12 + } 13 + }