| @@ -9,6 +9,7 @@ | ||
| "lint": "eslint" | ||
| }, | ||
| "dependencies": { | ||
| + | "@react-three/fiber": "^9.1.0", | |
| "@stepping/api": "workspace:*", | ||
| "@stepping/config": "workspace:*", | ||
| "@stepping/db": "workspace:*", | ||
| @@ -25,6 +26,7 @@ | ||
| "react": "19.2.4", | ||
| "react-dom": "19.2.4", | ||
| "superjson": "^2.2.2", | ||
| + | "three": "^0.171.0", | |
| "zod": "^3.24.1" | ||
| }, | ||
| "devDependencies": { | ||
| @@ -32,6 +34,7 @@ | ||
| "@types/node": "^20", | ||
| "@types/react": "^19", | ||
| "@types/react-dom": "^19", | ||
| + | "@types/three": "^0.171.0", | |
| "eslint": "^9", | ||
| "eslint-config-next": "16.2.9", | ||
| "tailwindcss": "^4", |
| @@ -66,6 +66,13 @@ export default async function CityPage({ | ||
| </p> | ||
| <h1 className="display mt-3 text-7xl sm:text-8xl">{city.name}</h1> | ||
| + | <Link | |
| + | href={`/city/${slug}/yard`} | |
| + | className="kicker mt-6 inline-block text-gold hover:text-gold-bright" | |
| + | > | |
| + | Enter The Yard → | |
| + | </Link> | |
| + | ||
| {/* Add an event */} | ||
| <section className="mt-14 border-t border-[var(--line)] pt-10"> | ||
| <div className="flex items-end justify-between"> |
| @@ -0,0 +1,496 @@ | ||
| + | import Link from "next/link"; | |
| + | import { notFound } from "next/navigation"; | |
| + | import { revalidatePath } from "next/cache"; | |
| + | import { serverCaller } from "@/lib/trpc/server"; | |
| + | ||
| + | export const dynamic = "force-dynamic"; | |
| + | ||
| + | export default async function YardPage({ | |
| + | params, | |
| + | }: { | |
| + | params: Promise<{ slug: string }>; | |
| + | }) { | |
| + | const { slug } = await params; | |
| + | const caller = serverCaller(); | |
| + | const city = await caller.city.bySlug({ slug }); | |
| + | if (!city) notFound(); | |
| + | ||
| + | const cityId = city.id; | |
| + | const [stepTeams, chapters, djs, promoters, classes, organizations] = | |
| + | await Promise.all([ | |
| + | caller.stepTeam.list({ cityId }), | |
| + | caller.chapter.list({ cityId }), | |
| + | caller.dj.list({ cityId }), | |
| + | caller.promoter.list({ cityId }), | |
| + | caller.class.list({ cityId }), | |
| + | caller.organization.list(), | |
| + | ]); | |
| + | ||
| + | const orgById = new Map(organizations.map((o) => [o.id, o])); | |
| + | ||
| + | async function addStepTeam(formData: FormData) { | |
| + | "use server"; | |
| + | const name = String(formData.get("name") ?? "").trim(); | |
| + | const affiliationOrgId = String(formData.get("affiliationOrgId") ?? ""); | |
| + | const bio = String(formData.get("bio") ?? "").trim(); | |
| + | if (!name) return; | |
| + | await serverCaller().stepTeam.create({ | |
| + | cityId: city!.id, | |
| + | name, | |
| + | affiliationOrgId: affiliationOrgId || undefined, | |
| + | bio: bio || undefined, | |
| + | }); | |
| + | revalidatePath(`/city/${slug}/yard`); | |
| + | } | |
| + | ||
| + | async function addChapter(formData: FormData) { | |
| + | "use server"; | |
| + | const organizationId = String(formData.get("organizationId") ?? ""); | |
| + | const name = String(formData.get("name") ?? "").trim(); | |
| + | const school = String(formData.get("school") ?? "").trim(); | |
| + | const bio = String(formData.get("bio") ?? "").trim(); | |
| + | if (!name || !organizationId) return; | |
| + | await serverCaller().chapter.create({ | |
| + | organizationId, | |
| + | cityId: city!.id, | |
| + | name, | |
| + | school: school || undefined, | |
| + | bio: bio || undefined, | |
| + | }); | |
| + | revalidatePath(`/city/${slug}/yard`); | |
| + | } | |
| + | ||
| + | async function addDj(formData: FormData) { | |
| + | "use server"; | |
| + | const name = String(formData.get("name") ?? "").trim(); | |
| + | const bio = String(formData.get("bio") ?? "").trim(); | |
| + | if (!name) return; | |
| + | await serverCaller().dj.create({ | |
| + | cityId: city!.id, | |
| + | name, | |
| + | bio: bio || undefined, | |
| + | }); | |
| + | revalidatePath(`/city/${slug}/yard`); | |
| + | } | |
| + | ||
| + | async function addPromoter(formData: FormData) { | |
| + | "use server"; | |
| + | const name = String(formData.get("name") ?? "").trim(); | |
| + | const bio = String(formData.get("bio") ?? "").trim(); | |
| + | if (!name) return; | |
| + | await serverCaller().promoter.create({ | |
| + | cityId: city!.id, | |
| + | name, | |
| + | bio: bio || undefined, | |
| + | }); | |
| + | revalidatePath(`/city/${slug}/yard`); | |
| + | } | |
| + | ||
| + | async function addClass(formData: FormData) { | |
| + | "use server"; | |
| + | const title = String(formData.get("title") ?? "").trim(); | |
| + | const instructor = String(formData.get("instructor") ?? "").trim(); | |
| + | const schedule = String(formData.get("schedule") ?? "").trim(); | |
| + | const isFree = formData.get("isFree") != null; | |
| + | if (!title) return; | |
| + | await serverCaller().class.create({ | |
| + | cityId: city!.id, | |
| + | title, | |
| + | instructor: instructor || undefined, | |
| + | schedule: schedule || undefined, | |
| + | isFree, | |
| + | }); | |
| + | revalidatePath(`/city/${slug}/yard`); | |
| + | } | |
| + | ||
| + | return ( | |
| + | <main className="mx-auto w-full max-w-3xl px-5 py-16 sm:px-8"> | |
| + | <Link href={`/city/${slug}`} className="kicker hover:text-gold"> | |
| + | ← {city.name} | |
| + | </Link> | |
| + | ||
| + | <p className="kicker mt-10 text-ash"> | |
| + | The Yard · {city.metroLabel} | |
| + | </p> | |
| + | <h1 className="display mt-3 text-7xl sm:text-8xl">The Yard</h1> | |
| + | <p className="serif mt-5 max-w-xl text-xl text-ash"> | |
| + | The community behind the show - step teams, chapters, DJs, promoters, and | |
| + | classes keeping {city.name} on beat. | |
| + | </p> | |
| + | ||
| + | {/* Step Teams */} | |
| + | <section className="mt-16 border-t border-[var(--line)] pt-10"> | |
| + | <div className="flex items-end justify-between"> | |
| + | <div> | |
| + | <p className="kicker text-gold">Section 01</p> | |
| + | <h2 className="display mt-2 text-4xl">Step Teams</h2> | |
| + | </div> | |
| + | <span className="kicker text-ash"> | |
| + | {String(stepTeams.length).padStart(2, "0")} teams | |
| + | </span> | |
| + | </div> | |
| + | ||
| + | {stepTeams.length === 0 ? ( | |
| + | <p className="serif mt-8 text-xl text-ash"> | |
| + | No step teams yet. Put the first line on the yard below. | |
| + | </p> | |
| + | ) : ( | |
| + | <ul className="mt-6"> | |
| + | {stepTeams.map((t, i) => { | |
| + | const org = t.affiliationOrgId | |
| + | ? orgById.get(t.affiliationOrgId) | |
| + | : null; | |
| + | return ( | |
| + | <li | |
| + | key={t.id} | |
| + | className="flex items-center gap-5 border-b border-[var(--line)] py-6" | |
| + | > | |
| + | <span className="kicker w-8 text-gold"> | |
| + | {String(i + 1).padStart(2, "0")} | |
| + | </span> | |
| + | <div className="flex-1"> | |
| + | <p className="display text-2xl sm:text-3xl">{t.name}</p> | |
| + | {t.bio ? ( | |
| + | <p className="serif mt-1 text-lg text-ash">{t.bio}</p> | |
| + | ) : null} | |
| + | </div> | |
| + | {org ? ( | |
| + | <span className="kicker border border-[var(--line)] px-3 py-1"> | |
| + | {org.letters ?? org.name} | |
| + | </span> | |
| + | ) : null} | |
| + | </li> | |
| + | ); | |
| + | })} | |
| + | </ul> | |
| + | )} | |
| + | ||
| + | <form action={addStepTeam} className="mt-8 flex flex-col gap-3"> | |
| + | <input | |
| + | name="name" | |
| + | required | |
| + | placeholder="Team name (e.g. Vanguard Steppers)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <input | |
| + | name="bio" | |
| + | placeholder="One line about the team (optional)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <div className="mt-2 flex flex-wrap gap-4"> | |
| + | <select | |
| + | name="affiliationOrgId" | |
| + | defaultValue="" | |
| + | className="kicker border border-[var(--line)] bg-stage px-4 py-3 text-bone outline-none focus:border-gold" | |
| + | > | |
| + | <option value="" className="bg-stage"> | |
| + | NO AFFILIATION | |
| + | </option> | |
| + | {organizations.map((o) => ( | |
| + | <option key={o.id} value={o.id} className="bg-stage"> | |
| + | {((o.letters ? `${o.letters} ` : "") + o.name).toUpperCase()} | |
| + | </option> | |
| + | ))} | |
| + | </select> | |
| + | <button | |
| + | type="submit" | |
| + | className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright" | |
| + | > | |
| + | Add team | |
| + | </button> | |
| + | </div> | |
| + | </form> | |
| + | </section> | |
| + | ||
| + | {/* Chapters */} | |
| + | <section className="mt-16 border-t border-[var(--line)] pt-10"> | |
| + | <div className="flex items-end justify-between"> | |
| + | <div> | |
| + | <p className="kicker text-gold">Section 02</p> | |
| + | <h2 className="display mt-2 text-4xl">Chapters</h2> | |
| + | </div> | |
| + | <span className="kicker text-ash"> | |
| + | {String(chapters.length).padStart(2, "0")} chapters | |
| + | </span> | |
| + | </div> | |
| + | ||
| + | {chapters.length === 0 ? ( | |
| + | <p className="serif mt-8 text-xl text-ash"> | |
| + | No chapters yet. Register the first Divine Nine chapter below. | |
| + | </p> | |
| + | ) : ( | |
| + | <ul className="mt-6"> | |
| + | {chapters.map((c, i) => { | |
| + | const org = orgById.get(c.organizationId); | |
| + | return ( | |
| + | <li | |
| + | key={c.id} | |
| + | className="flex items-center gap-5 border-b border-[var(--line)] py-6" | |
| + | > | |
| + | <span className="kicker w-8 text-gold"> | |
| + | {String(i + 1).padStart(2, "0")} | |
| + | </span> | |
| + | <div className="flex-1"> | |
| + | <p className="display text-2xl sm:text-3xl">{c.name}</p> | |
| + | <p className="kicker mt-1 text-ash"> | |
| + | {c.school ? c.school.toUpperCase() : "CHAPTER"} | |
| + | </p> | |
| + | </div> | |
| + | {org ? ( | |
| + | <span className="kicker border border-[var(--line)] px-3 py-1"> | |
| + | {org.letters ?? org.name} | |
| + | </span> | |
| + | ) : null} | |
| + | </li> | |
| + | ); | |
| + | })} | |
| + | </ul> | |
| + | )} | |
| + | ||
| + | <form action={addChapter} className="mt-8 flex flex-col gap-3"> | |
| + | <input | |
| + | name="name" | |
| + | required | |
| + | placeholder="Chapter name (e.g. Alpha Beta Chapter)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <input | |
| + | name="school" | |
| + | placeholder="School or post (optional)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <div className="mt-2 flex flex-wrap gap-4"> | |
| + | <select | |
| + | name="organizationId" | |
| + | required | |
| + | defaultValue="" | |
| + | className="kicker border border-[var(--line)] bg-stage px-4 py-3 text-bone outline-none focus:border-gold" | |
| + | > | |
| + | <option value="" disabled className="bg-stage"> | |
| + | SELECT ORGANIZATION | |
| + | </option> | |
| + | {organizations.map((o) => ( | |
| + | <option key={o.id} value={o.id} className="bg-stage"> | |
| + | {((o.letters ? `${o.letters} ` : "") + o.name).toUpperCase()} | |
| + | </option> | |
| + | ))} | |
| + | </select> | |
| + | <button | |
| + | type="submit" | |
| + | className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright" | |
| + | > | |
| + | Add chapter | |
| + | </button> | |
| + | </div> | |
| + | </form> | |
| + | </section> | |
| + | ||
| + | {/* DJs */} | |
| + | <section className="mt-16 border-t border-[var(--line)] pt-10"> | |
| + | <div className="flex items-end justify-between"> | |
| + | <div> | |
| + | <p className="kicker text-gold">Section 03</p> | |
| + | <h2 className="display mt-2 text-4xl">DJs</h2> | |
| + | </div> | |
| + | <span className="kicker text-ash"> | |
| + | {String(djs.length).padStart(2, "0")} djs | |
| + | </span> | |
| + | </div> | |
| + | ||
| + | {djs.length === 0 ? ( | |
| + | <p className="serif mt-8 text-xl text-ash"> | |
| + | No DJs yet. Spin up the first one below. | |
| + | </p> | |
| + | ) : ( | |
| + | <ul className="mt-6"> | |
| + | {djs.map((d, i) => ( | |
| + | <li | |
| + | key={d.id} | |
| + | className="flex items-center gap-5 border-b border-[var(--line)] py-6" | |
| + | > | |
| + | <span className="kicker w-8 text-gold"> | |
| + | {String(i + 1).padStart(2, "0")} | |
| + | </span> | |
| + | <div className="flex-1"> | |
| + | <p className="display text-2xl sm:text-3xl">{d.name}</p> | |
| + | {d.bio ? ( | |
| + | <p className="serif mt-1 text-lg text-ash">{d.bio}</p> | |
| + | ) : null} | |
| + | </div> | |
| + | <span className="kicker border border-[var(--line)] px-3 py-1"> | |
| + | DJ | |
| + | </span> | |
| + | </li> | |
| + | ))} | |
| + | </ul> | |
| + | )} | |
| + | ||
| + | <form action={addDj} className="mt-8 flex flex-col gap-3"> | |
| + | <input | |
| + | name="name" | |
| + | required | |
| + | placeholder="DJ name (e.g. DJ Cadence)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <input | |
| + | name="bio" | |
| + | placeholder="One line (optional)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <div className="mt-2"> | |
| + | <button | |
| + | type="submit" | |
| + | className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright" | |
| + | > | |
| + | Add DJ | |
| + | </button> | |
| + | </div> | |
| + | </form> | |
| + | </section> | |
| + | ||
| + | {/* Promoters */} | |
| + | <section className="mt-16 border-t border-[var(--line)] pt-10"> | |
| + | <div className="flex items-end justify-between"> | |
| + | <div> | |
| + | <p className="kicker text-gold">Section 04</p> | |
| + | <h2 className="display mt-2 text-4xl">Promoters</h2> | |
| + | </div> | |
| + | <span className="kicker text-ash"> | |
| + | {String(promoters.length).padStart(2, "0")} promoters | |
| + | </span> | |
| + | </div> | |
| + | ||
| + | {promoters.length === 0 ? ( | |
| + | <p className="serif mt-8 text-xl text-ash"> | |
| + | No promoters yet. Name the first one putting on the show below. | |
| + | </p> | |
| + | ) : ( | |
| + | <ul className="mt-6"> | |
| + | {promoters.map((p, i) => ( | |
| + | <li | |
| + | key={p.id} | |
| + | className="flex items-center gap-5 border-b border-[var(--line)] py-6" | |
| + | > | |
| + | <span className="kicker w-8 text-gold"> | |
| + | {String(i + 1).padStart(2, "0")} | |
| + | </span> | |
| + | <div className="flex-1"> | |
| + | <p className="display text-2xl sm:text-3xl">{p.name}</p> | |
| + | {p.bio ? ( | |
| + | <p className="serif mt-1 text-lg text-ash">{p.bio}</p> | |
| + | ) : null} | |
| + | </div> | |
| + | <span className="kicker border border-[var(--line)] px-3 py-1"> | |
| + | PROMOTER | |
| + | </span> | |
| + | </li> | |
| + | ))} | |
| + | </ul> | |
| + | )} | |
| + | ||
| + | <form action={addPromoter} className="mt-8 flex flex-col gap-3"> | |
| + | <input | |
| + | name="name" | |
| + | required | |
| + | placeholder="Promoter or brand (e.g. Yardline Events)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <input | |
| + | name="bio" | |
| + | placeholder="One line (optional)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-2 text-lg text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <div className="mt-2"> | |
| + | <button | |
| + | type="submit" | |
| + | className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright" | |
| + | > | |
| + | Add promoter | |
| + | </button> | |
| + | </div> | |
| + | </form> | |
| + | </section> | |
| + | ||
| + | {/* Classes */} | |
| + | <section className="mt-16 border-t border-[var(--line)] pt-10"> | |
| + | <div className="flex items-end justify-between"> | |
| + | <div> | |
| + | <p className="kicker text-gold">Section 05</p> | |
| + | <h2 className="display mt-2 text-4xl">Classes</h2> | |
| + | </div> | |
| + | <span className="kicker text-ash"> | |
| + | {String(classes.length).padStart(2, "0")} classes | |
| + | </span> | |
| + | </div> | |
| + | ||
| + | {classes.length === 0 ? ( | |
| + | <p className="serif mt-8 text-xl text-ash"> | |
| + | No classes yet. Teach the first line below. | |
| + | </p> | |
| + | ) : ( | |
| + | <ul className="mt-6"> | |
| + | {classes.map((c, i) => ( | |
| + | <li | |
| + | key={c.id} | |
| + | className="flex items-center gap-5 border-b border-[var(--line)] py-6" | |
| + | > | |
| + | <span className="kicker w-8 text-gold"> | |
| + | {String(i + 1).padStart(2, "0")} | |
| + | </span> | |
| + | <div className="flex-1"> | |
| + | <p className="display text-2xl sm:text-3xl">{c.title}</p> | |
| + | <p className="kicker mt-1 text-ash"> | |
| + | {[ | |
| + | c.instructor ? `WITH ${c.instructor.toUpperCase()}` : null, | |
| + | c.schedule ? c.schedule.toUpperCase() : null, | |
| + | ] | |
| + | .filter(Boolean) | |
| + | .join(" · ") || "SCHEDULE TBD"} | |
| + | </p> | |
| + | </div> | |
| + | <span className="kicker border border-[var(--line)] px-3 py-1"> | |
| + | {c.isFree ? "FREE" : "CLASS"} | |
| + | </span> | |
| + | </li> | |
| + | ))} | |
| + | </ul> | |
| + | )} | |
| + | ||
| + | <form action={addClass} className="mt-8 flex flex-col gap-3"> | |
| + | <input | |
| + | name="title" | |
| + | required | |
| + | placeholder="Class title (e.g. Intro to Stepping)" | |
| + | className="serif border-b border-[var(--line)] bg-transparent py-3 text-2xl text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <div className="mt-2 flex flex-wrap items-center gap-4"> | |
| + | <input | |
| + | name="instructor" | |
| + | placeholder="Instructor" | |
| + | className="kicker border border-[var(--line)] bg-stage px-4 py-3 text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <input | |
| + | name="schedule" | |
| + | placeholder="Schedule (e.g. Tuesdays 7pm)" | |
| + | className="kicker border border-[var(--line)] bg-stage px-4 py-3 text-bone outline-none placeholder:text-ash focus:border-gold" | |
| + | /> | |
| + | <label className="kicker flex items-center gap-2 text-ash"> | |
| + | <input | |
| + | type="checkbox" | |
| + | name="isFree" | |
| + | className="h-4 w-4 accent-gold" | |
| + | /> | |
| + | Free | |
| + | </label> | |
| + | <button | |
| + | type="submit" | |
| + | className="kicker bg-gold px-6 py-3 text-stage hover:bg-gold-bright" | |
| + | > | |
| + | Add class | |
| + | </button> | |
| + | </div> | |
| + | </form> | |
| + | </section> | |
| + | </main> | |
| + | ); | |
| + | } |
| @@ -1,6 +1,7 @@ | ||
| import Link from "next/link"; | ||
| import { serverCaller } from "@/lib/trpc/server"; | ||
| import { Marquee } from "@/components/marquee"; | ||
| + | import { HeroCanvas } from "@/components/hero-canvas"; | |
| export const dynamic = "force-dynamic"; | ||
| @@ -62,7 +63,9 @@ export default async function Home() { | ||
| </header> | ||
| {/* STAGE */} | ||
| - | <section className="flex min-h-[100svh] flex-col justify-center px-5 pt-24 sm:px-8"> | |
| + | <section className="relative flex min-h-[100svh] flex-col justify-center overflow-hidden px-5 pt-24 sm:px-8"> | |
| + | <HeroCanvas /> | |
| + | <div className="relative z-10"> | |
| <p className="kicker beat-in b1">On Beat / Divine Nine Stepping</p> | ||
| <h1 className="display mt-6 text-[20vw] leading-[0.82] sm:text-[16vw]"> | ||
| <span className="block beat-in b1">STOMP.</span> | ||
| @@ -85,6 +88,7 @@ export default async function Home() { | ||
| ))} | ||
| <span className="kicker text-ash">More cities loading</span> | ||
| </div> | ||
| + | </div> | |
| </section> | ||
| <Marquee /> |
| @@ -0,0 +1,22 @@ | ||
| + | "use client"; | |
| + | ||
| + | import dynamic from "next/dynamic"; | |
| + | import { useEffect, useState } from "react"; | |
| + | ||
| + | const HeroScene = dynamic(() => import("./hero-scene"), { ssr: false }); | |
| + | ||
| + | export function HeroCanvas() { | |
| + | const [show, setShow] = useState(false); | |
| + | ||
| + | useEffect(() => { | |
| + | const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; | |
| + | if (!reduce) setShow(true); | |
| + | }, []); | |
| + | ||
| + | if (!show) return null; | |
| + | return ( | |
| + | <div className="pointer-events-none absolute inset-0 z-0 opacity-90"> | |
| + | <HeroScene /> | |
| + | </div> | |
| + | ); | |
| + | } |
| @@ -0,0 +1,115 @@ | ||
| + | "use client"; | |
| + | ||
| + | import { Canvas, useFrame, useThree } from "@react-three/fiber"; | |
| + | import { useEffect, useMemo, useRef } from "react"; | |
| + | import * as THREE from "three"; | |
| + | ||
| + | const vertex = /* glsl */ ` | |
| + | varying vec2 vUv; | |
| + | void main() { | |
| + | vUv = uv; | |
| + | gl_Position = vec4(position.x, position.y, 0.0, 1.0); | |
| + | } | |
| + | `; | |
| + | ||
| + | // Stomp shockwave stage-floor. Concentric gold rings emanate on a beat and | |
| + | // react to the cursor, on the warm-black stage. One signature object, subtle. | |
| + | const fragment = /* glsl */ ` | |
| + | precision highp float; | |
| + | varying vec2 vUv; | |
| + | uniform float uTime; | |
| + | uniform vec2 uMouse; | |
| + | uniform float uAspect; | |
| + | ||
| + | const vec3 STAGE = vec3(0.059, 0.055, 0.047); | |
| + | const vec3 GOLD = vec3(0.784, 0.635, 0.298); | |
| + | ||
| + | float hash(vec2 p) { | |
| + | return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); | |
| + | } | |
| + | ||
| + | void main() { | |
| + | vec2 p = vUv - 0.5; | |
| + | p.x *= uAspect; | |
| + | vec2 m = uMouse - 0.5; | |
| + | m.x *= uAspect; | |
| + | ||
| + | float d = length(p); | |
| + | float beat = 0.82 + 0.18 * sin(uTime * 100.0 / 60.0 * 3.14159); | |
| + | ||
| + | float rings = 0.0; | |
| + | float t = uTime * 0.42; | |
| + | for (int i = 0; i < 4; i++) { | |
| + | float phase = fract(t + float(i) * 0.25); | |
| + | float radius = phase * 1.05; | |
| + | float band = smoothstep(0.018, 0.0, abs(d - radius)); | |
| + | rings += band * (1.0 - phase); | |
| + | } | |
| + | rings *= beat; | |
| + | ||
| + | float md = length(p - m); | |
| + | float cursor = smoothstep(0.32, 0.0, md); | |
| + | float cursorRing = smoothstep(0.01, 0.0, abs(md - 0.14)) * 0.6; | |
| + | ||
| + | float energy = rings * 0.55 + cursor * 0.28 + cursorRing; | |
| + | vec3 col = STAGE + GOLD * energy * 0.6; | |
| + | ||
| + | col *= smoothstep(1.25, 0.15, d); | |
| + | col += (hash(vUv * 900.0 + uTime) - 0.5) * 0.012; | |
| + | ||
| + | gl_FragColor = vec4(col, 1.0); | |
| + | } | |
| + | `; | |
| + | ||
| + | function Floor() { | |
| + | const mat = useRef<THREE.ShaderMaterial>(null); | |
| + | const target = useRef(new THREE.Vector2(0.5, 0.5)); | |
| + | const { size } = useThree(); | |
| + | ||
| + | const uniforms = useMemo( | |
| + | () => ({ | |
| + | uTime: { value: 0 }, | |
| + | uMouse: { value: new THREE.Vector2(0.5, 0.5) }, | |
| + | uAspect: { value: 1 }, | |
| + | }), | |
| + | [], | |
| + | ); | |
| + | ||
| + | useEffect(() => { | |
| + | const onMove = (e: MouseEvent) => { | |
| + | target.current.set(e.clientX / window.innerWidth, 1 - e.clientY / window.innerHeight); | |
| + | }; | |
| + | window.addEventListener("mousemove", onMove); | |
| + | return () => window.removeEventListener("mousemove", onMove); | |
| + | }, []); | |
| + | ||
| + | useFrame((_, dt) => { | |
| + | const u = mat.current?.uniforms; | |
| + | if (!u) return; | |
| + | u.uTime.value += dt; | |
| + | u.uAspect.value = size.width / size.height; | |
| + | const m = u.uMouse.value as THREE.Vector2; | |
| + | m.x += (target.current.x - m.x) * 0.06; | |
| + | m.y += (target.current.y - m.y) * 0.06; | |
| + | }); | |
| + | ||
| + | return ( | |
| + | <mesh> | |
| + | <planeGeometry args={[2, 2]} /> | |
| + | <shaderMaterial | |
| + | ref={mat} | |
| + | uniforms={uniforms} | |
| + | vertexShader={vertex} | |
| + | fragmentShader={fragment} | |
| + | /> | |
| + | </mesh> | |
| + | ); | |
| + | } | |
| + | ||
| + | export default function HeroScene() { | |
| + | return ( | |
| + | <Canvas dpr={[1, 2]} gl={{ antialias: true }} style={{ width: "100%", height: "100%" }}> | |
| + | <Floor /> | |
| + | </Canvas> | |
| + | ); | |
| + | } |
| @@ -8,6 +8,7 @@ import { | ||
| djs, | ||
| events, | ||
| flags, | ||
| + | organizations, | |
| promoters, | ||
| stepTeams, | ||
| videos, | ||
| @@ -46,6 +47,13 @@ export const appRouter = router({ | ||
| ), | ||
| }), | ||
| + | // ---- Organizations (Divine Nine reference set) --------------------------- | |
| + | organization: router({ | |
| + | list: publicProcedure.query(({ ctx }) => | |
| + | ctx.db.select().from(organizations), | |
| + | ), | |
| + | }), | |
| + | ||
| // ---- Events -------------------------------------------------------------- | ||
| event: router({ | ||
| list: publicProcedure |
| @@ -20,6 +20,9 @@ importers: | ||
| apps/web: | ||
| dependencies: | ||
| + | '@react-three/fiber': | |
| + | specifier: ^9.1.0 | |
| + | version: 9.6.1(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.171.0) | |
| '@stepping/api': | ||
| specifier: workspace:* | ||
| version: link:../../packages/api | ||
| @@ -68,6 +71,9 @@ importers: | ||
| superjson: | ||
| specifier: ^2.2.2 | ||
| version: 2.2.6 | ||
| + | three: | |
| + | specifier: ^0.171.0 | |
| + | version: 0.171.0 | |
| zod: | ||
| specifier: ^3.24.1 | ||
| version: 3.25.76 | ||
| @@ -84,6 +90,9 @@ importers: | ||
| '@types/react-dom': | ||
| specifier: ^19 | ||
| version: 19.2.3(@types/react@19.2.17) | ||
| + | '@types/three': | |
| + | specifier: ^0.171.0 | |
| + | version: 0.171.0 | |
| eslint: | ||
| specifier: ^9 | ||
| version: 9.39.4(jiti@2.7.0) | ||
| @@ -241,6 +250,10 @@ packages: | ||
| engines: {node: '>=6.0.0'} | ||
| hasBin: true | ||
| + | '@babel/runtime@7.29.7': | |
| + | resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} | |
| + | engines: {node: '>=6.9.0'} | |
| + | ||
| '@babel/template@7.29.7': | ||
| resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} | ||
| engines: {node: '>=6.9.0'} | ||
| @@ -992,6 +1005,31 @@ packages: | ||
| '@petamoriken/float16@3.9.3': | ||
| resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==} | ||
| + | '@react-three/fiber@9.6.1': | |
| + | resolution: {integrity: sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==} | |
| + | peerDependencies: | |
| + | expo: '>=43.0' | |
| + | expo-asset: '>=8.4' | |
| + | expo-file-system: '>=11.0' | |
| + | expo-gl: '>=11.0' | |
| + | react: '>=19 <19.3' | |
| + | react-dom: '>=19 <19.3' | |
| + | react-native: '>=0.78' | |
| + | three: '>=0.156' | |
| + | peerDependenciesMeta: | |
| + | expo: | |
| + | optional: true | |
| + | expo-asset: | |
| + | optional: true | |
| + | expo-file-system: | |
| + | optional: true | |
| + | expo-gl: | |
| + | optional: true | |
| + | react-dom: | |
| + | optional: true | |
| + | react-native: | |
| + | optional: true | |
| + | ||
| '@rtsao/scc@1.1.0': | ||
| resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} | ||
| @@ -1178,6 +1216,9 @@ packages: | ||
| cpu: [arm64] | ||
| os: [win32] | ||
| + | '@tweenjs/tween.js@23.1.3': | |
| + | resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} | |
| + | ||
| '@tybys/wasm-util@0.10.3': | ||
| resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} | ||
| @@ -1201,9 +1242,23 @@ packages: | ||
| peerDependencies: | ||
| '@types/react': ^19.2.0 | ||
| + | '@types/react-reconciler@0.28.9': | |
| + | resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} | |
| + | peerDependencies: | |
| + | '@types/react': '*' | |
| + | ||
| '@types/react@19.2.17': | ||
| resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} | ||
| + | '@types/stats.js@0.17.4': | |
| + | resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} | |
| + | ||
| + | '@types/three@0.171.0': | |
| + | resolution: {integrity: sha512-oLuT1SAsT+CUg/wxUTFHo0K3NtJLnx9sJhZWQJp/0uXqFpzSk1hRHmvWvpaAWSfvx2db0lVKZ5/wV0I0isD2mQ==} | |
| + | ||
| + | '@types/webxr@0.5.24': | |
| + | resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} | |
| + | ||
| '@typescript-eslint/eslint-plugin@8.62.0': | ||
| resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==} | ||
| engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} | ||
| @@ -1373,6 +1428,9 @@ packages: | ||
| cpu: [x64] | ||
| os: [win32] | ||
| + | '@webgpu/types@0.1.71': | |
| + | resolution: {integrity: sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==} | |
| + | ||
| acorn-jsx@5.3.2: | ||
| resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} | ||
| peerDependencies: | ||
| @@ -1455,6 +1513,9 @@ packages: | ||
| resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} | ||
| engines: {node: 18 || 20 || >=22} | ||
| + | base64-js@1.5.1: | |
| + | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} | |
| + | ||
| baseline-browser-mapping@2.10.38: | ||
| resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==} | ||
| engines: {node: '>=6.0.0'} | ||
| @@ -1479,6 +1540,9 @@ packages: | ||
| buffer-from@1.1.2: | ||
| resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} | ||
| + | buffer@6.0.3: | |
| + | resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} | |
| + | ||
| call-bind-apply-helpers@1.0.2: | ||
| resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} | ||
| engines: {node: '>= 0.4'} | ||
| @@ -1911,6 +1975,9 @@ packages: | ||
| picomatch: | ||
| optional: true | ||
| + | fflate@0.8.3: | |
| + | resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} | |
| + | ||
| file-entry-cache@8.0.0: | ||
| resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} | ||
| engines: {node: '>=16.0.0'} | ||
| @@ -2041,6 +2108,9 @@ packages: | ||
| resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} | ||
| engines: {node: '>=20.0.0'} | ||
| + | ieee754@1.2.1: | |
| + | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} | |
| + | ||
| ignore@5.3.2: | ||
| resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} | ||
| engines: {node: '>= 4'} | ||
| @@ -2186,6 +2256,11 @@ packages: | ||
| resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} | ||
| engines: {node: '>= 0.4'} | ||
| + | its-fine@2.0.0: | |
| + | resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==} | |
| + | peerDependencies: | |
| + | react: ^19.0.0 | |
| + | ||
| jiti@2.7.0: | ||
| resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} | ||
| hasBin: true | ||
| @@ -2347,6 +2422,9 @@ packages: | ||
| resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} | ||
| engines: {node: '>= 8'} | ||
| + | meshoptimizer@0.18.1: | |
| + | resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} | |
| + | ||
| micromatch@4.0.8: | ||
| resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} | ||
| engines: {node: '>=8.6'} | ||
| @@ -2523,6 +2601,15 @@ packages: | ||
| react-is@16.13.1: | ||
| resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} | ||
| + | react-use-measure@2.1.7: | |
| + | resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} | |
| + | peerDependencies: | |
| + | react: '>=16.13' | |
| + | react-dom: '>=16.13' | |
| + | peerDependenciesMeta: | |
| + | react-dom: | |
| + | optional: true | |
| + | ||
| react@19.2.4: | ||
| resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} | ||
| engines: {node: '>=0.10.0'} | ||
| @@ -2696,6 +2783,11 @@ packages: | ||
| resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} | ||
| engines: {node: '>= 0.4'} | ||
| + | suspend-react@0.1.3: | |
| + | resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} | |
| + | peerDependencies: | |
| + | react: '>=17.0' | |
| + | ||
| tailwindcss@4.3.1: | ||
| resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==} | ||
| @@ -2703,6 +2795,9 @@ packages: | ||
| resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} | ||
| engines: {node: '>=6'} | ||
| + | three@0.171.0: | |
| + | resolution: {integrity: sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==} | |
| + | ||
| tinyglobby@0.2.17: | ||
| resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} | ||
| engines: {node: '>=12.0.0'} | ||
| @@ -2783,6 +2878,11 @@ packages: | ||
| uri-js@4.4.1: | ||
| resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} | ||
| + | use-sync-external-store@1.6.0: | |
| + | resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} | |
| + | peerDependencies: | |
| + | react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 | |
| + | ||
| which-boxed-primitive@1.1.1: | ||
| resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} | ||
| engines: {node: '>= 0.4'} | ||
| @@ -2829,6 +2929,24 @@ packages: | ||
| zod@3.25.76: | ||
| resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} | ||
| + | zustand@5.0.14: | |
| + | resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==} | |
| + | engines: {node: '>=12.20.0'} | |
| + | peerDependencies: | |
| + | '@types/react': '>=18.0.0' | |
| + | immer: '>=9.0.6' | |
| + | react: '>=18.0.0' | |
| + | use-sync-external-store: '>=1.2.0' | |
| + | peerDependenciesMeta: | |
| + | '@types/react': | |
| + | optional: true | |
| + | immer: | |
| + | optional: true | |
| + | react: | |
| + | optional: true | |
| + | use-sync-external-store: | |
| + | optional: true | |
| + | ||
| snapshots: | ||
| '@alloc/quick-lru@5.2.0': {} | ||
| @@ -2910,6 +3028,8 @@ snapshots: | ||
| dependencies: | ||
| '@babel/types': 7.29.7 | ||
| + | '@babel/runtime@7.29.7': {} | |
| + | ||
| '@babel/template@7.29.7': | ||
| dependencies: | ||
| '@babel/code-frame': 7.29.7 | ||
| @@ -3410,6 +3530,26 @@ snapshots: | ||
| '@petamoriken/float16@3.9.3': {} | ||
| + | '@react-three/fiber@9.6.1(@types/react@19.2.17)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.171.0)': | |
| + | dependencies: | |
| + | '@babel/runtime': 7.29.7 | |
| + | '@types/webxr': 0.5.24 | |
| + | base64-js: 1.5.1 | |
| + | buffer: 6.0.3 | |
| + | its-fine: 2.0.0(@types/react@19.2.17)(react@19.2.4) | |
| + | react: 19.2.4 | |
| + | react-use-measure: 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) | |
| + | scheduler: 0.27.0 | |
| + | suspend-react: 0.1.3(react@19.2.4) | |
| + | three: 0.171.0 | |
| + | use-sync-external-store: 1.6.0(react@19.2.4) | |
| + | zustand: 5.0.14(@types/react@19.2.17)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) | |
| + | optionalDependencies: | |
| + | react-dom: 19.2.4(react@19.2.4) | |
| + | transitivePeerDependencies: | |
| + | - '@types/react' | |
| + | - immer | |
| + | ||
| '@rtsao/scc@1.1.0': {} | ||
| '@supabase/auth-js@2.108.2': | ||
| @@ -3565,6 +3705,8 @@ snapshots: | ||
| '@turbo/windows-arm64@2.10.0': | ||
| optional: true | ||
| + | '@tweenjs/tween.js@23.1.3': {} | |
| + | ||
| '@tybys/wasm-util@0.10.3': | ||
| dependencies: | ||
| tslib: 2.8.1 | ||
| @@ -3586,10 +3728,27 @@ snapshots: | ||
| dependencies: | ||
| '@types/react': 19.2.17 | ||
| + | '@types/react-reconciler@0.28.9(@types/react@19.2.17)': | |
| + | dependencies: | |
| + | '@types/react': 19.2.17 | |
| + | ||
| '@types/react@19.2.17': | ||
| dependencies: | ||
| csstype: 3.2.3 | ||
| + | '@types/stats.js@0.17.4': {} | |
| + | ||
| + | '@types/three@0.171.0': | |
| + | dependencies: | |
| + | '@tweenjs/tween.js': 23.1.3 | |
| + | '@types/stats.js': 0.17.4 | |
| + | '@types/webxr': 0.5.24 | |
| + | '@webgpu/types': 0.1.71 | |
| + | fflate: 0.8.3 | |
| + | meshoptimizer: 0.18.1 | |
| + | ||
| + | '@types/webxr@0.5.24': {} | |
| + | ||
| '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': | ||
| dependencies: | ||
| '@eslint-community/regexpp': 4.12.2 | ||
| @@ -3751,6 +3910,8 @@ snapshots: | ||
| '@unrs/resolver-binding-win32-x64-msvc@1.12.2': | ||
| optional: true | ||
| + | '@webgpu/types@0.1.71': {} | |
| + | ||
| acorn-jsx@5.3.2(acorn@8.17.0): | ||
| dependencies: | ||
| acorn: 8.17.0 | ||
| @@ -3855,6 +4016,8 @@ snapshots: | ||
| balanced-match@4.0.4: {} | ||
| + | base64-js@1.5.1: {} | |
| + | ||
| baseline-browser-mapping@2.10.38: {} | ||
| brace-expansion@1.1.15: | ||
| @@ -3880,6 +4043,11 @@ snapshots: | ||
| buffer-from@1.1.2: {} | ||
| + | buffer@6.0.3: | |
| + | dependencies: | |
| + | base64-js: 1.5.1 | |
| + | ieee754: 1.2.1 | |
| + | ||
| call-bind-apply-helpers@1.0.2: | ||
| dependencies: | ||
| es-errors: 1.3.0 | ||
| @@ -4443,6 +4611,8 @@ snapshots: | ||
| optionalDependencies: | ||
| picomatch: 4.0.4 | ||
| + | fflate@0.8.3: {} | |
| + | ||
| file-entry-cache@8.0.0: | ||
| dependencies: | ||
| flat-cache: 4.0.1 | ||
| @@ -4580,6 +4750,8 @@ snapshots: | ||
| iceberg-js@0.8.1: {} | ||
| + | ieee754@1.2.1: {} | |
| + | ||
| ignore@5.3.2: {} | ||
| ignore@7.0.5: {} | ||
| @@ -4730,6 +4902,13 @@ snapshots: | ||
| has-symbols: 1.1.0 | ||
| set-function-name: 2.0.2 | ||
| + | its-fine@2.0.0(@types/react@19.2.17)(react@19.2.4): | |
| + | dependencies: | |
| + | '@types/react-reconciler': 0.28.9(@types/react@19.2.17) | |
| + | react: 19.2.4 | |
| + | transitivePeerDependencies: | |
| + | - '@types/react' | |
| + | ||
| jiti@2.7.0: {} | ||
| js-tokens@4.0.0: {} | ||
| @@ -4849,6 +5028,8 @@ snapshots: | ||
| merge2@1.4.1: {} | ||
| + | meshoptimizer@0.18.1: {} | |
| + | ||
| micromatch@4.0.8: | ||
| dependencies: | ||
| braces: 3.0.3 | ||
| @@ -5023,6 +5204,12 @@ snapshots: | ||
| react-is@16.13.1: {} | ||
| + | react-use-measure@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): | |
| + | dependencies: | |
| + | react: 19.2.4 | |
| + | optionalDependencies: | |
| + | react-dom: 19.2.4(react@19.2.4) | |
| + | ||
| react@19.2.4: {} | ||
| reflect.getprototypeof@1.0.10: | ||
| @@ -5267,10 +5454,16 @@ snapshots: | ||
| supports-preserve-symlinks-flag@1.0.0: {} | ||
| + | suspend-react@0.1.3(react@19.2.4): | |
| + | dependencies: | |
| + | react: 19.2.4 | |
| + | ||
| tailwindcss@4.3.1: {} | ||
| tapable@2.3.3: {} | ||
| + | three@0.171.0: {} | |
| + | ||
| tinyglobby@0.2.17: | ||
| dependencies: | ||
| fdir: 6.5.0(picomatch@4.0.4) | ||
| @@ -5404,6 +5597,10 @@ snapshots: | ||
| dependencies: | ||
| punycode: 2.3.1 | ||
| + | use-sync-external-store@1.6.0(react@19.2.4): | |
| + | dependencies: | |
| + | react: 19.2.4 | |
| + | ||
| which-boxed-primitive@1.1.1: | ||
| dependencies: | ||
| is-bigint: 1.1.0 | ||
| @@ -5464,3 +5661,9 @@ snapshots: | ||
| zod: 3.25.76 | ||
| zod@3.25.76: {} | ||
| + | ||
| + | zustand@5.0.14(@types/react@19.2.17)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): | |
| + | optionalDependencies: | |
| + | '@types/react': 19.2.17 | |
| + | react: 19.2.4 | |
| + | use-sync-external-store: 1.6.0(react@19.2.4) |