← back to Nineoh Guide
iteration 1: Turborepo scaffold + working Next.js web MVP (SSR home, DB health, legal routes) + shared @nineoh/core zod contracts
3c6110482fdf7ecff5bc3b8bc8eddf23aedf55ca · 2026-07-25 09:56:53 -0700 · Steve
Files touched
A .npmrcA apps/web/app/api/health/route.tsA apps/web/app/layout.tsxA apps/web/app/legal/disclaimer/page.tsxA apps/web/app/legal/dmca/page.tsxA apps/web/app/legal/privacy/page.tsxA apps/web/app/page.tsxA apps/web/lib/db.tsA apps/web/next-env.d.tsA apps/web/next.config.mjsA apps/web/package.jsonA apps/web/tsconfig.jsonA package.jsonA packages/core/package.jsonA packages/core/src/index.tsA packages/core/src/schemas.tsA pnpm-lock.yamlA pnpm-workspace.yamlA turbo.json
Diff
commit 3c6110482fdf7ecff5bc3b8bc8eddf23aedf55ca
Author: Steve <steve@designerwallcoverings.com>
Date: Sat Jul 25 09:56:53 2026 -0700
iteration 1: Turborepo scaffold + working Next.js web MVP (SSR home, DB health, legal routes) + shared @nineoh/core zod contracts
---
.npmrc | 1 +
apps/web/app/api/health/route.ts | 14 +
apps/web/app/layout.tsx | 65 +++
apps/web/app/legal/disclaimer/page.tsx | 30 ++
apps/web/app/legal/dmca/page.tsx | 39 ++
apps/web/app/legal/privacy/page.tsx | 39 ++
apps/web/app/page.tsx | 70 +++
apps/web/lib/db.ts | 28 ++
apps/web/next-env.d.ts | 6 +
apps/web/next.config.mjs | 9 +
apps/web/package.json | 25 ++
apps/web/tsconfig.json | 21 +
package.json | 19 +
packages/core/package.json | 13 +
packages/core/src/index.ts | 9 +
packages/core/src/schemas.ts | 75 ++++
pnpm-lock.yaml | 793 +++++++++++++++++++++++++++++++++
pnpm-workspace.yaml | 5 +
turbo.json | 8 +
19 files changed, 1269 insertions(+)
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..d099685
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+verify-deps-before-run=false
diff --git a/apps/web/app/api/health/route.ts b/apps/web/app/api/health/route.ts
new file mode 100644
index 0000000..2dd2d52
--- /dev/null
+++ b/apps/web/app/api/health/route.ts
@@ -0,0 +1,14 @@
+import { NextResponse } from "next/server";
+import { dbOk } from "@/lib/db";
+
+export const dynamic = "force-dynamic";
+
+export async function GET() {
+ const db = await dbOk();
+ return NextResponse.json({
+ ok: true,
+ service: "nineoh-web",
+ db: db ? "up" : "down",
+ ts: new Date().toISOString(),
+ });
+}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
new file mode 100644
index 0000000..73c91d8
--- /dev/null
+++ b/apps/web/app/layout.tsx
@@ -0,0 +1,65 @@
+import type { Metadata } from "next";
+import { APP } from "@nineoh/core";
+
+export const metadata: Metadata = {
+ title: {
+ default: `${APP.displayName} — Unofficial Episode & Cast Guide`,
+ template: `%s · ${APP.displayName}`,
+ },
+ description:
+ "An unofficial, fan-made guide to the 90210 series: original episode recaps, cast bios, and news. Not affiliated with the show's producers, network, or cast.",
+ robots: { index: true, follow: true },
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+ <html lang="en">
+ <body
+ style={{
+ margin: 0,
+ fontFamily:
+ "system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
+ color: "#1a1a1a",
+ background: "#fafafa",
+ }}
+ >
+ <header
+ style={{
+ padding: "12px 20px",
+ borderBottom: "1px solid #e5e5e5",
+ display: "flex",
+ gap: 12,
+ alignItems: "baseline",
+ }}
+ >
+ <strong style={{ fontSize: 18 }}>{APP.displayName}</strong>
+ <span style={{ fontSize: 12, color: "#8a1c1c", fontWeight: 600 }}>
+ UNOFFICIAL
+ </span>
+ </header>
+ <main style={{ maxWidth: 820, margin: "0 auto", padding: "24px 20px" }}>
+ {children}
+ </main>
+ <footer
+ style={{
+ maxWidth: 820,
+ margin: "0 auto",
+ padding: "24px 20px",
+ borderTop: "1px solid #e5e5e5",
+ fontSize: 12,
+ color: "#666",
+ }}
+ >
+ {APP.disclaimerShort}{" "}
+ <a href="/legal/disclaimer">Disclaimer</a> ·{" "}
+ <a href="/legal/dmca">Copyright/DMCA</a> ·{" "}
+ <a href="/legal/privacy">Privacy</a>
+ </footer>
+ </body>
+ </html>
+ );
+}
diff --git a/apps/web/app/legal/disclaimer/page.tsx b/apps/web/app/legal/disclaimer/page.tsx
new file mode 100644
index 0000000..00ca9c4
--- /dev/null
+++ b/apps/web/app/legal/disclaimer/page.tsx
@@ -0,0 +1,30 @@
+export const metadata = { title: "Disclaimer" };
+
+export default function Disclaimer() {
+ return (
+ <article style={{ lineHeight: 1.7 }}>
+ <h1>Disclaimer</h1>
+ <p>
+ <strong>This is an unofficial fan-made guide.</strong> The Unofficial
+ 90210 Guide is an independent, fan-created reference and is <em>not</em>{" "}
+ affiliated with, authorized, endorsed, or sponsored by the producers,
+ network, distributors, cast, or any rights-holders of the television
+ series it discusses. All series titles, character names, and trademarks
+ referenced belong to their respective owners and are used here for
+ identification and editorial commentary only.
+ </p>
+ <p>
+ All episode summaries, cast biographies, and news write-ups in this app
+ are <strong>original works</strong> written by our editorial team. We do
+ not reproduce official synopses, promotional artwork, logos, or
+ press-kit photography. Images shown are either in the public domain,
+ offered under a Creative Commons license, or used under a separate
+ license we have obtained; each carries its own attribution.
+ </p>
+ <p>
+ If you believe any material infringes your rights, please see our{" "}
+ <a href="/legal/dmca">Copyright / DMCA Policy</a>.
+ </p>
+ </article>
+ );
+}
diff --git a/apps/web/app/legal/dmca/page.tsx b/apps/web/app/legal/dmca/page.tsx
new file mode 100644
index 0000000..0fd618a
--- /dev/null
+++ b/apps/web/app/legal/dmca/page.tsx
@@ -0,0 +1,39 @@
+export const metadata = { title: "Copyright / DMCA Policy" };
+
+export default function Dmca() {
+ return (
+ <article style={{ lineHeight: 1.7 }}>
+ <h1>Copyright / DMCA Policy</h1>
+ <p>
+ We respect intellectual property rights and respond to notices of
+ alleged infringement under the Digital Millennium Copyright Act (17
+ U.S.C. § 512).
+ </p>
+ <h2>Designated agent</h2>
+ <p>
+ Send notices to our designated DMCA contact:{" "}
+ <strong>dmca@[app-domain]</strong> (set at launch).
+ </p>
+ <h2>A valid notice must include</h2>
+ <ol>
+ <li>Identification of the copyrighted work claimed to be infringed.</li>
+ <li>Identification and location (URL / screen) of the material.</li>
+ <li>Your contact information.</li>
+ <li>A good-faith-belief statement.</li>
+ <li>A statement, under penalty of perjury, of accuracy and authority.</li>
+ <li>Your physical or electronic signature.</li>
+ </ol>
+ <h2>Our process</h2>
+ <p>
+ We triage promptly, may disable disputed material while investigating,
+ notify the uploader, provide a counter-notice path, and reinstate only
+ if the statutory process is satisfied.
+ </p>
+ <h2>Repeat-infringer policy</h2>
+ <p>
+ We terminate, in appropriate circumstances, the accounts of users who
+ are repeat infringers.
+ </p>
+ </article>
+ );
+}
diff --git a/apps/web/app/legal/privacy/page.tsx b/apps/web/app/legal/privacy/page.tsx
new file mode 100644
index 0000000..b1df225
--- /dev/null
+++ b/apps/web/app/legal/privacy/page.tsx
@@ -0,0 +1,39 @@
+export const metadata = { title: "Privacy Policy" };
+
+export default function Privacy() {
+ return (
+ <article style={{ lineHeight: 1.7 }}>
+ <h1>Privacy Policy</h1>
+ <p>
+ This policy supports accurate Apple App Store privacy labels and Google
+ Play Data Safety disclosures, including the practices of integrated
+ third-party SDKs.
+ </p>
+ <h2>What we collect</h2>
+ <ul>
+ <li>Account data (if you sign up): email, display name.</li>
+ <li>User content: reviews, comments, lists you choose to post.</li>
+ <li>Usage & diagnostics: interactions and crash logs.</li>
+ <li>Advertising / analytics IDs: only if enabled and per your consent.</li>
+ </ul>
+ <h2>Tracking & consent</h2>
+ <p>
+ On the web we use a consent banner with Google Consent Mode v2 (default
+ denied until you choose). In the app we present Google UMP and, on iOS,
+ the App Tracking Transparency prompt before any cross-app tracking. You
+ can change choices any time in Settings → Privacy.
+ </p>
+ <h2>Your rights</h2>
+ <p>
+ Request access, correction, or deletion via{" "}
+ <strong>privacy@[app-domain]</strong> (set at launch). We honor
+ applicable CCPA/CPRA rights and do not sell personal information.
+ </p>
+ <h2>Children</h2>
+ <p>
+ This app is not directed to children under 13 and we do not knowingly
+ collect their personal information.
+ </p>
+ </article>
+ );
+}
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
new file mode 100644
index 0000000..1978cc2
--- /dev/null
+++ b/apps/web/app/page.tsx
@@ -0,0 +1,70 @@
+import { pool } from "@/lib/db";
+
+export const dynamic = "force-dynamic";
+
+async function getStats() {
+ try {
+ const show = await pool.query(
+ "select display_title, description_original from shows limit 1"
+ );
+ const eps = await pool.query("select count(*)::int as n from episodes");
+ const cast = await pool.query("select count(*)::int as n from cast_people");
+ return {
+ show: show.rows[0] ?? null,
+ episodes: eps.rows[0]?.n ?? 0,
+ cast: cast.rows[0]?.n ?? 0,
+ };
+ } catch {
+ return { show: null, episodes: 0, cast: 0 };
+ }
+}
+
+export default async function Home() {
+ const { show, episodes, cast } = await getStats();
+ return (
+ <section>
+ <h1 style={{ fontSize: 30, marginBottom: 4 }}>
+ {show?.display_title ?? "Unofficial 90210 Guide"}
+ </h1>
+ <p style={{ color: "#444", lineHeight: 1.6 }}>
+ {show?.description_original ??
+ "A fan-made companion to the 90210 series — original episode recaps, cast bios, and news. This is an unofficial guide, written independently."}
+ </p>
+
+ <div style={{ display: "flex", gap: 16, margin: "20px 0" }}>
+ <Stat label="Episodes indexed" value={episodes} />
+ <Stat label="Cast profiles" value={cast} />
+ </div>
+
+ <nav style={{ display: "grid", gap: 8 }}>
+ <a href="/episodes">Browse episode guide →</a>
+ <a href="/cast">Browse cast →</a>
+ <a href="/news">Latest news →</a>
+ </nav>
+
+ {episodes === 0 && (
+ <p style={{ marginTop: 24, fontSize: 13, color: "#8a1c1c" }}>
+ Content is being curated. Episode and cast data will populate as our
+ editors write original recaps and clear image rights.
+ </p>
+ )}
+ </section>
+ );
+}
+
+function Stat({ label, value }: { label: string; value: number }) {
+ return (
+ <div
+ style={{
+ border: "1px solid #e5e5e5",
+ borderRadius: 8,
+ padding: "12px 16px",
+ background: "#fff",
+ minWidth: 140,
+ }}
+ >
+ <div style={{ fontSize: 24, fontWeight: 700 }}>{value}</div>
+ <div style={{ fontSize: 12, color: "#666" }}>{label}</div>
+ </div>
+ );
+}
diff --git a/apps/web/lib/db.ts b/apps/web/lib/db.ts
new file mode 100644
index 0000000..bcea804
--- /dev/null
+++ b/apps/web/lib/db.ts
@@ -0,0 +1,28 @@
+import { Pool } from "pg";
+
+/**
+ * Single shared pg Pool. In dev we connect to the local Postgres socket; in
+ * production DATABASE_URL points at Neon/Vercel Postgres. Cached on globalThis
+ * so Next's hot-reload doesn't open a new pool per request.
+ */
+const globalForDb = globalThis as unknown as { _pgPool?: Pool };
+
+export const pool =
+ globalForDb._pgPool ??
+ new Pool({
+ connectionString:
+ process.env.DATABASE_URL ??
+ "postgresql://localhost/nineoh_guide?host=/tmp",
+ max: 5,
+ });
+
+if (process.env.NODE_ENV !== "production") globalForDb._pgPool = pool;
+
+export async function dbOk(): Promise<boolean> {
+ try {
+ const { rows } = await pool.query("select 1 as ok");
+ return rows[0]?.ok === 1;
+ } catch {
+ return false;
+ }
+}
diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts
new file mode 100644
index 0000000..830fb59
--- /dev/null
+++ b/apps/web/next-env.d.ts
@@ -0,0 +1,6 @@
+/// <reference types="next" />
+/// <reference types="next/image-types/global" />
+/// <reference path="./.next/types/routes.d.ts" />
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
new file mode 100644
index 0000000..efb324c
--- /dev/null
+++ b/apps/web/next.config.mjs
@@ -0,0 +1,9 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+ // packages/core is TS source consumed directly — let Next transpile it.
+ transpilePackages: ["@nineoh/core"],
+ // pg is a server-only dependency; keep it out of the client bundle.
+ serverExternalPackages: ["pg"],
+};
+export default nextConfig;
diff --git a/apps/web/package.json b/apps/web/package.json
new file mode 100644
index 0000000..7a36ec2
--- /dev/null
+++ b/apps/web/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@nineoh/web",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev -p 4090",
+ "build": "next build",
+ "start": "next start -p 4090",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@nineoh/core": "workspace:*",
+ "next": "^15.1.3",
+ "pg": "^8.13.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.2",
+ "@types/pg": "^8.11.10",
+ "@types/react": "^19.0.2",
+ "typescript": "^5.7.2"
+ }
+}
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
new file mode 100644
index 0000000..f13bc90
--- /dev/null
+++ b/apps/web/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["dom", "dom.iterable", "ES2022"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [{ "name": "next" }],
+ "paths": { "@/*": ["./*"] }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f6dc9bd
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "nineoh-guide",
+ "private": true,
+ "version": "0.1.0",
+ "description": "Unofficial 90210 Guide — Turborepo monorepo (Next.js web + Expo mobile + shared core)",
+ "packageManager": "pnpm@11.15.1",
+ "scripts": {
+ "dev": "turbo run dev",
+ "build": "turbo run build",
+ "lint": "turbo run lint",
+ "web": "pnpm --filter @nineoh/web dev",
+ "mobile": "pnpm --filter @nineoh/mobile start",
+ "db:apply": "psql \"$DATABASE_URL\" -f db/schema.sql",
+ "db:seed": "node db/seed.mjs"
+ },
+ "devDependencies": {
+ "turbo": "^2.3.3"
+ }
+}
diff --git a/packages/core/package.json b/packages/core/package.json
new file mode 100644
index 0000000..644b612
--- /dev/null
+++ b/packages/core/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "@nineoh/core",
+ "version": "0.1.0",
+ "private": true,
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "dependencies": {
+ "zod": "^3.24.1"
+ }
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
new file mode 100644
index 0000000..da27c7c
--- /dev/null
+++ b/packages/core/src/index.ts
@@ -0,0 +1,9 @@
+export * from "./schemas";
+
+/** App-wide constants shared by web + mobile. */
+export const APP = {
+ displayName: "Unofficial 90210 Guide",
+ unofficial: true,
+ disclaimerShort:
+ "Unofficial, fan-made. Not affiliated with or endorsed by the series' producers, network, or cast.",
+} as const;
diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts
new file mode 100644
index 0000000..82df3e8
--- /dev/null
+++ b/packages/core/src/schemas.ts
@@ -0,0 +1,75 @@
+import { z } from "zod";
+
+/**
+ * Shared zod schemas — the single source of truth for data contracts across
+ * the Next.js web app and the Expo mobile app. Web forms and mobile screens
+ * validate against the SAME schema, so the two apps can never drift.
+ *
+ * Design note: every user-facing text field that could carry third-party
+ * expression is named `*_original` to encode the hard rail that summaries and
+ * bios are ORIGINAL, never copied synopses.
+ */
+
+export const AssetSchema = z.object({
+ id: z.string().uuid(),
+ type: z.enum(["image", "thumbnail"]),
+ fileUrl: z.string().url().nullable(), // null while PENDING a cleared license
+ thumbnailUrl: z.string().url().nullable(),
+ licenseType: z.enum([
+ "public-domain",
+ "cc-by",
+ "cc-by-sa",
+ "licensed",
+ "PENDING",
+ ]),
+ licenseUrl: z.string().url().nullable(),
+ attributionText: z.string().nullable(),
+ usageScope: z.enum(["editorial", "marketing", "icon"]).default("editorial"),
+ approvedForMarketing: z.boolean().default(false),
+ licensePurchaseStatus: z
+ .enum(["none", "drafted-for-approval", "purchased"])
+ .default("none"),
+});
+export type Asset = z.infer<typeof AssetSchema>;
+
+/** An asset may only render if it has a cleared, non-PENDING license. */
+export function assetIsRenderable(a: Asset): boolean {
+ return a.fileUrl != null && a.licenseType !== "PENDING";
+}
+
+export const EpisodeSchema = z.object({
+ id: z.string().uuid(),
+ seasonNumber: z.number().int().positive(),
+ episodeNumber: z.number().int().positive(),
+ title: z.string().min(1),
+ airDate: z.string().nullable(), // ISO date
+ writer: z.string().nullable(),
+ director: z.string().nullable(),
+ summaryShortOriginal: z.string().nullable(),
+ summaryFullOriginal: z.string().nullable(),
+ spoilerRating: z.number().int().min(0).max(3).default(0),
+ externalIds: z.record(z.string()).default({}),
+});
+export type Episode = z.infer<typeof EpisodeSchema>;
+
+export const CastPersonSchema = z.object({
+ id: z.string().uuid(),
+ name: z.string().min(1),
+ biographyOriginal: z.string().nullable(),
+ officialSiteUrl: z.string().url().nullable(),
+ verifiedSocialLinks: z.array(z.string().url()).default([]),
+ headshotAssetId: z.string().uuid().nullable(),
+ publicityRiskLevel: z.enum(["editorial-only", "cleared"]).default("editorial-only"),
+});
+export type CastPerson = z.infer<typeof CastPersonSchema>;
+
+export const NewsItemSchema = z.object({
+ id: z.string().uuid(),
+ headline: z.string().min(1),
+ summaryOriginal: z.string().min(1), // one-sentence ORIGINAL summary
+ canonicalUrl: z.string().url(),
+ publisherName: z.string().nullable(),
+ publishedAt: z.string().nullable(),
+ topicTags: z.array(z.string()).default([]),
+});
+export type NewsItem = z.infer<typeof NewsItemSchema>;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..887e849
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,793 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ turbo:
+ specifier: ^2.3.3
+ version: 2.10.6
+
+ apps/web:
+ dependencies:
+ '@nineoh/core':
+ specifier: workspace:*
+ version: link:../../packages/core
+ next:
+ specifier: ^15.1.3
+ version: 15.5.21(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+ pg:
+ specifier: ^8.13.1
+ version: 8.22.0
+ react:
+ specifier: ^19.0.0
+ version: 19.2.8
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.2.8(react@19.2.8)
+ zod:
+ specifier: ^3.24.1
+ version: 3.25.76
+ devDependencies:
+ '@types/node':
+ specifier: ^22.10.2
+ version: 22.20.1
+ '@types/pg':
+ specifier: ^8.11.10
+ version: 8.20.0
+ '@types/react':
+ specifier: ^19.0.2
+ version: 19.2.17
+ typescript:
+ specifier: ^5.7.2
+ version: 5.9.3
+
+ packages/core:
+ dependencies:
+ zod:
+ specifier: ^3.24.1
+ version: 3.25.76
+
+packages:
+
+ '@emnapi/runtime@1.11.2':
+ resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==}
+
+ '@img/colour@1.1.0':
+ resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.5':
+ resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linux-arm64@0.34.5':
+ resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-arm@0.34.5':
+ resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-s390x@0.34.5':
+ resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linux-x64@0.34.5':
+ resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@img/sharp-wasm32@0.34.5':
+ resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.5':
+ resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.5':
+ resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.5':
+ resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@next/env@15.5.21':
+ resolution: {integrity: sha512-hjJI/GfrjWHgNguRIBzItjRRu0m3Nrz17GhxsjuHfjIvg9hyg3239REd2dpI+bpMTFuVrVprHzEQ19m++cDtbw==}
+
+ '@next/swc-darwin-arm64@15.5.21':
+ resolution: {integrity: sha512-ZfjqPEdi6TRC/fWx7UDbwb1fbVgyh2uD5tVTRKIDZDlYM+UNuE/LafDG2fwuAoZilADpABh46OY/F5qf9JjqLQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@15.5.21':
+ resolution: {integrity: sha512-TlCf1NpxgQLzTrexuev75xwmNCJMd1/qkJpTVP1GRRcih93hlIBn1P72hkh8T0gnRFr6BmWksQtbyG3jT6jnww==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@15.5.21':
+ resolution: {integrity: sha512-LXRsq1p+HvHSi7ygwNcSEEcK0zuo5jS75ZlqFHtOH+LF7qntXAJVJxah+1Pi/GyBm7EpkwU7m4EgbvIKrMqm9A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-arm64-musl@15.5.21':
+ resolution: {integrity: sha512-hyGixhFxpDKjqoev6l4KlcRBlt9AXWrGhDZwmwg49sMJM5tnKQPSi+SEj9+e5n+l/bthRGZUdh59GKIs6lQPRw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-linux-x64-gnu@15.5.21':
+ resolution: {integrity: sha512-qfE+YfOba6S2+13e8qn1/UozDVNZ2clBlrs8UtDoax4s8ediu6sq93z66OEHUYlb69Tffh5JTNkgtsAKiSuugg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-x64-musl@15.5.21':
+ resolution: {integrity: sha512-BXLGG+EvIwp/Rrgl6HY8sqvD6BOUOIRz8/naDbeLNX7mlA5H2XRcL6MW/0IGnJISfj5BA9gNhFyJj5yOoiIDJQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-win32-arm64-msvc@15.5.21':
+ resolution: {integrity: sha512-tNGNOlT0Wn7E4IMsSnufjXN/l2L2/AGdLLpa2vzS89SYCBuihgLn3ngLsIrvndAnWo9nAkus+4gZHTI/Ijx9HA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@15.5.21':
+ resolution: {integrity: sha512-DmIdWmC9p4rdNIiQqo8ap0+Cnj6kKtTZnuSCxoYydSc8sgpDgAg9wFhxplunak9imLV0pTvc5WVCOHwm5eHLtQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@turbo/darwin-64@2.10.6':
+ resolution: {integrity: sha512-S+yIyZkmjYBQbIM7sTqMAwnLeMjSK60Q9grUDPbTWvkRjbsn5k1Rt5sPvTX80M4PnlYQBwLiYsjlu6tHsuIqQQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@turbo/darwin-arm64@2.10.6':
+ resolution: {integrity: sha512-gnS8G78EjvJzDc1+tFES5BYXdlF+292n+h57G9G+5LJYelRh195f4Ed22RCo4EFIbI4Du9S5xfE9YjrqhoZaxQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@turbo/linux-64@2.10.6':
+ resolution: {integrity: sha512-lPv5AVkzvhs4z6Isr2ZE2UYt9Ndym5aWSMEIRh9OROnAdyEG1CFugJwAn/aFuDMmiCoHasyvD5tTDmVVTTrYxw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@turbo/linux-arm64@2.10.6':
+ resolution: {integrity: sha512-yeQ24es8pz+FhZxt25HIXj4ml7HpWMrmOL1BtCP4XUz7mBJZdAfC7z7HGF+wPSUZoKmoSZbL4r4QWbRddhPZIQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@turbo/windows-64@2.10.6':
+ resolution: {integrity: sha512-JiFscBN83F43VG3oM+QU2LvgHcf11tgirLJ3c1QDJBtulTCbDwupzIjqB9yBlfiMWAe6g3ssD6gqLP6a4g7FzA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@turbo/windows-arm64@2.10.6':
+ resolution: {integrity: sha512-emWVdriXsaSVS5VXJxy4DppQ0UnGD+PUKu40DGjC3E5JXeoszL09B/1xU6B+Y8lJQg38cFoaB1NqGC4ayY5SQQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@types/node@22.20.1':
+ resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==}
+
+ '@types/pg@8.20.0':
+ resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
+
+ '@types/react@19.2.17':
+ resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
+
+ caniuse-lite@1.0.30001806:
+ resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ nanoid@3.3.16:
+ resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ next@15.5.21:
+ resolution: {integrity: sha512-/TsdBtkWLhkl+NVL3Uqws2UphNd6IPzOtzSk1fHaf+0P7GQKLZDUytyhns/Ykbzdy9+YRjwG7ONvrHaaTDdFqQ==}
+ engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
+ hasBin: true
+ 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
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
+ pg-cloudflare@1.4.0:
+ resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==}
+
+ pg-connection-string@2.14.0:
+ resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==}
+
+ pg-int8@1.0.1:
+ resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
+ engines: {node: '>=4.0.0'}
+
+ pg-pool@3.14.0:
+ resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==}
+ peerDependencies:
+ pg: '>=8.0'
+
+ pg-protocol@1.15.0:
+ resolution: {integrity: sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==}
+
+ pg-types@2.2.0:
+ resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
+ engines: {node: '>=4'}
+
+ pg@8.22.0:
+ resolution: {integrity: sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==}
+ engines: {node: '>= 16.0.0'}
+ peerDependencies:
+ pg-native: '>=3.0.1'
+ peerDependenciesMeta:
+ pg-native:
+ optional: true
+
+ pgpass@1.0.5:
+ resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postgres-array@2.0.0:
+ resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
+ engines: {node: '>=4'}
+
+ postgres-bytea@1.0.1:
+ resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-date@1.0.7:
+ resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-interval@1.2.0:
+ resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
+ engines: {node: '>=0.10.0'}
+
+ react-dom@19.2.8:
+ resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==}
+ peerDependencies:
+ react: ^19.2.8
+
+ react@19.2.8:
+ resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==}
+ engines: {node: '>=0.10.0'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ turbo@2.10.6:
+ resolution: {integrity: sha512-3VfH7fK7WQ/ZHYjvfWyVnPRpcNaX3ZqXfGDMdc82eGdM0ESATyxFK4R4PM3wNsVFsdkUFZ4pZpkNeG37dxOv+g==}
+ hasBin: true
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ xtend@4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
+snapshots:
+
+ '@emnapi/runtime@1.11.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@img/colour@1.1.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-wasm32@0.34.5':
+ dependencies:
+ '@emnapi/runtime': 1.11.2
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.5':
+ optional: true
+
+ '@next/env@15.5.21': {}
+
+ '@next/swc-darwin-arm64@15.5.21':
+ optional: true
+
+ '@next/swc-darwin-x64@15.5.21':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@15.5.21':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@15.5.21':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@15.5.21':
+ optional: true
+
+ '@next/swc-linux-x64-musl@15.5.21':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@15.5.21':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@15.5.21':
+ optional: true
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@turbo/darwin-64@2.10.6':
+ optional: true
+
+ '@turbo/darwin-arm64@2.10.6':
+ optional: true
+
+ '@turbo/linux-64@2.10.6':
+ optional: true
+
+ '@turbo/linux-arm64@2.10.6':
+ optional: true
+
+ '@turbo/windows-64@2.10.6':
+ optional: true
+
+ '@turbo/windows-arm64@2.10.6':
+ optional: true
+
+ '@types/node@22.20.1':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/pg@8.20.0':
+ dependencies:
+ '@types/node': 22.20.1
+ pg-protocol: 1.15.0
+ pg-types: 2.2.0
+
+ '@types/react@19.2.17':
+ dependencies:
+ csstype: 3.2.3
+
+ caniuse-lite@1.0.30001806: {}
+
+ client-only@0.0.1: {}
+
+ csstype@3.2.3: {}
+
+ detect-libc@2.1.2:
+ optional: true
+
+ nanoid@3.3.16: {}
+
+ next@15.5.21(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
+ dependencies:
+ '@next/env': 15.5.21
+ '@swc/helpers': 0.5.15
+ caniuse-lite: 1.0.30001806
+ postcss: 8.4.31
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ styled-jsx: 5.1.6(react@19.2.8)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 15.5.21
+ '@next/swc-darwin-x64': 15.5.21
+ '@next/swc-linux-arm64-gnu': 15.5.21
+ '@next/swc-linux-arm64-musl': 15.5.21
+ '@next/swc-linux-x64-gnu': 15.5.21
+ '@next/swc-linux-x64-musl': 15.5.21
+ '@next/swc-win32-arm64-msvc': 15.5.21
+ '@next/swc-win32-x64-msvc': 15.5.21
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ pg-cloudflare@1.4.0:
+ optional: true
+
+ pg-connection-string@2.14.0: {}
+
+ pg-int8@1.0.1: {}
+
+ pg-pool@3.14.0(pg@8.22.0):
+ dependencies:
+ pg: 8.22.0
+
+ pg-protocol@1.15.0: {}
+
+ pg-types@2.2.0:
+ dependencies:
+ pg-int8: 1.0.1
+ postgres-array: 2.0.0
+ postgres-bytea: 1.0.1
+ postgres-date: 1.0.7
+ postgres-interval: 1.2.0
+
+ pg@8.22.0:
+ dependencies:
+ pg-connection-string: 2.14.0
+ pg-pool: 3.14.0(pg@8.22.0)
+ pg-protocol: 1.15.0
+ pg-types: 2.2.0
+ pgpass: 1.0.5
+ optionalDependencies:
+ pg-cloudflare: 1.4.0
+
+ pgpass@1.0.5:
+ dependencies:
+ split2: 4.2.0
+
+ picocolors@1.1.1: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.16
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postgres-array@2.0.0: {}
+
+ postgres-bytea@1.0.1: {}
+
+ postgres-date@1.0.7: {}
+
+ postgres-interval@1.2.0:
+ dependencies:
+ xtend: 4.0.2
+
+ react-dom@19.2.8(react@19.2.8):
+ dependencies:
+ react: 19.2.8
+ scheduler: 0.27.0
+
+ react@19.2.8: {}
+
+ scheduler@0.27.0: {}
+
+ semver@7.8.5:
+ optional: true
+
+ sharp@0.34.5:
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.8.5
+ 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
+ optional: true
+
+ source-map-js@1.2.1: {}
+
+ split2@4.2.0: {}
+
+ styled-jsx@5.1.6(react@19.2.8):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.8
+
+ tslib@2.8.1: {}
+
+ turbo@2.10.6:
+ optionalDependencies:
+ '@turbo/darwin-64': 2.10.6
+ '@turbo/darwin-arm64': 2.10.6
+ '@turbo/linux-64': 2.10.6
+ '@turbo/linux-arm64': 2.10.6
+ '@turbo/windows-64': 2.10.6
+ '@turbo/windows-arm64': 2.10.6
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
+
+ xtend@4.0.2: {}
+
+ zod@3.25.76: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..c19a8d5
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,5 @@
+packages:
+ - "apps/*"
+ - "packages/*"
+allowBuilds:
+ sharp: true
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 0000000..1242755
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://turbo.build/schema.json",
+ "tasks": {
+ "build": { "dependsOn": ["^build"], "outputs": [".next/**", "!.next/cache/**", "dist/**"] },
+ "dev": { "cache": false, "persistent": true },
+ "lint": {}
+ }
+}
← ade979f add rights-first Postgres schema + original legal pages (dis
·
back to Nineoh Guide
·
iteration 2: Expo/EAS scaffold (TestFlight config) + shared 648b098 →