diff --git a/cdn/cms-bulk-redirects/.env.example b/cdn/cms-bulk-redirects/.env.example
new file mode 100644
index 0000000000..b63a47363d
--- /dev/null
+++ b/cdn/cms-bulk-redirects/.env.example
@@ -0,0 +1,2 @@
+CONTENTFUL_SPACE_ID=your_space_id
+CONTENTFUL_ACCESS_TOKEN=your_cda_token
diff --git a/cdn/cms-bulk-redirects/.eslintrc.json b/cdn/cms-bulk-redirects/.eslintrc.json
new file mode 100644
index 0000000000..a2569c2c7c
--- /dev/null
+++ b/cdn/cms-bulk-redirects/.eslintrc.json
@@ -0,0 +1,4 @@
+{
+ "root": true,
+ "extends": "next/core-web-vitals"
+}
diff --git a/cdn/cms-bulk-redirects/.gitignore b/cdn/cms-bulk-redirects/.gitignore
new file mode 100644
index 0000000000..2214749b41
--- /dev/null
+++ b/cdn/cms-bulk-redirects/.gitignore
@@ -0,0 +1,43 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# Dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# Testing
+/coverage
+
+# Next.js
+/.next/
+/out/
+next-env.d.ts
+
+# Production
+build
+dist
+
+# Misc
+.DS_Store
+*.pem
+
+# Debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Local ENV files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Vercel
+.vercel
+
+# Turborepo
+.turbo
+
+# typescript
+*.tsbuildinfo
+.env*.local
diff --git a/cdn/cms-bulk-redirects/README.md b/cdn/cms-bulk-redirects/README.md
new file mode 100644
index 0000000000..084f5b3c9f
--- /dev/null
+++ b/cdn/cms-bulk-redirects/README.md
@@ -0,0 +1,57 @@
+---
+name: Contentful CMS bulk redirects (vercel.ts)
+slug: cms-bulk-redirects
+description: Sync redirect entries from Contentful into Vercel bulk redirects using vercel.ts.
+framework: Next.js
+useCase: Redirects
+css: Tailwind
+deployUrl: https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/cdn/cms-bulk-redirects&project-name=cms-bulk-redirects&repository-name=cms-bulk-redirects&env=CONTENTFUL_SPACE_ID,CONTENTFUL_ACCESS_TOKEN
+demoUrl: https://cms-bulk-redirects.vercel.app
+---
+
+# Contentful CMS bulk redirects (vercel.ts) example
+
+This example shows how to pull redirect entries from Contentful at build time, write them to a bulk redirects file, and publish them with the new `vercel.ts` config. The demo uses an e-commerce catalog so marketing can rotate seasonal URLs without shipping code.
+
+## Demo
+
+https://cms-bulk-redirects.vercel.app
+
+## How to Use
+
+You can choose from one of the following two methods to use this repository:
+
+### One-Click Deploy
+
+Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples):
+
+[](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/cdn/cms-bulk-redirects&project-name=cms-bulk-redirects&repository-name=cms-bulk-redirects&env=CONTENTFUL_SPACE_ID,CONTENTFUL_ACCESS_TOKEN)
+
+### Clone and Deploy
+
+Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
+
+```bash
+pnpm create next-app --example https://github.com/vercel/examples/tree/main/cdn/cms-bulk-redirects
+```
+
+Next, run Next.js in development mode:
+
+```bash
+pnpm dev
+```
+
+## Environment variables
+
+- `CONTENTFUL_SPACE_ID` – Contentful space ID
+- `CONTENTFUL_ACCESS_TOKEN` – Content Delivery API token (CDA)
+
+The example ships a small `generated-redirects.json` for local runs. When the environment variables are present, `vercel.ts` fetches real entries from Contentful and rewrites the bulk redirects file before build.
+
+## How it works
+
+1. `vercel.ts` runs at build time. It pulls `redirect` entries from Contentful, transforms them into Vercel bulk redirect objects, and writes `generated-redirects.json`.
+2. The `config` exported from `vercel.ts` sets `bulkRedirectsPath` to that file. Vercel publishes the redirects without touching Next.js routing, middleware, or edge functions.
+3. The UI shows an e-commerce catalog with collections that map to redirect targets like `/catalog/fall-2025` or `/catalog/limited-edition`. Legacy vanity paths such as `/catalog/fall` or `/products/daybreak-pack` are captured by bulk redirects.
+
+You can extend this pattern to any CMS: swap the fetch logic, keep the same `bulkRedirectsPath`.
diff --git a/cdn/cms-bulk-redirects/app/about/page.tsx b/cdn/cms-bulk-redirects/app/about/page.tsx
new file mode 100644
index 0000000000..95eb970a91
--- /dev/null
+++ b/cdn/cms-bulk-redirects/app/about/page.tsx
@@ -0,0 +1,191 @@
+import Link from 'next/link'
+
+export default function About() {
+ return (
+
+ {/* Header */}
+
+
+
+ ← Back to store
+
+
+ How it works
+
+
+ This demo uses Vercel's bulk redirects feature to manage seasonal URLs without code changes.
+
+
+
+
+ {/* Content */}
+
+
+ {/* The Problem */}
+
+
+ The problem
+
+
+ E-commerce sites often need vanity URLs that stay consistent while the content behind them changes:
+
+
+
+ •
+ /catalog/fall should always show the current fall collection
+
+
+ •
+ /catalog/latest should point to the newest drop
+
+
+ •
+ Retired product SKUs should redirect to relevant collections
+
+
+
+
+ {/* The Solution */}
+
+
+ The solution
+
+
+ Vercel's bulk redirects let you manage thousands of redirects at the edge—no middleware, no server-side logic.
+
+
+
+
1. Define redirects in a JSON file
+
+ Or fetch them from a CMS like Contentful, Sanity, or any API
+
+
+
+
2. Use vercel.ts to generate at build time
+
+ The config file runs during build and outputs the redirect rules
+
+
+
+
3. Redirects execute at the edge
+
+ Fast, globally distributed, no app code involved
+
+
+
+
+
+ {/* Code Example */}
+
+
+ Example code
+
+
+{`// vercel.ts
+import type { VercelConfig } from '@vercel/config/v1'
+import { writeFileSync } from 'fs'
+
+const redirects = [
+ {
+ source: '/catalog/fall',
+ destination: '/catalog/fall-2025',
+ statusCode: 302
+ },
+ {
+ source: '/catalog/latest',
+ destination: '/catalog/spring-2026',
+ permanent: true
+ }
+]
+
+writeFileSync(
+ 'generated-redirects.json',
+ JSON.stringify(redirects, null, 2)
+)
+
+export const config: VercelConfig = {
+ bulkRedirectsPath: './generated-redirects.json',
+}`}
+
+
+
+ {/* Try it */}
+
+
+ Try it
+
+
+ Click these links to see the redirects in action:
+
+
+
+
/catalog/fall
+
→ fall-2025
+
+
+
/catalog/winter
+
→ winter-2025
+
+
+
/catalog/latest
+
→ spring-2026
+
+
+
/catalog/outlet
+
→ archive
+
+
+
+
+ {/* Resources */}
+
+
+
+
+ )
+}
diff --git a/cdn/cms-bulk-redirects/app/api/redirects/route.ts b/cdn/cms-bulk-redirects/app/api/redirects/route.ts
new file mode 100644
index 0000000000..c07d94f298
--- /dev/null
+++ b/cdn/cms-bulk-redirects/app/api/redirects/route.ts
@@ -0,0 +1,16 @@
+import { NextResponse } from 'next/server'
+import { readFileSync } from 'fs'
+import { join } from 'path'
+
+export async function GET() {
+ try {
+ const filePath = join(process.cwd(), 'generated-redirects.json')
+ const contents = readFileSync(filePath, 'utf-8')
+ return new NextResponse(contents, {
+ status: 200,
+ headers: { 'content-type': 'application/json' },
+ })
+ } catch (error) {
+ return NextResponse.json({ error: 'Redirect file not found' }, { status: 500 })
+ }
+}
diff --git a/cdn/cms-bulk-redirects/app/catalog/[collection]/page.tsx b/cdn/cms-bulk-redirects/app/catalog/[collection]/page.tsx
new file mode 100644
index 0000000000..cc2976521e
--- /dev/null
+++ b/cdn/cms-bulk-redirects/app/catalog/[collection]/page.tsx
@@ -0,0 +1,96 @@
+import { notFound } from 'next/navigation'
+import Link from 'next/link'
+import { getCollection, getCollectionParams } from '../../../lib/collections'
+
+export async function generateStaticParams() {
+ return getCollectionParams()
+}
+
+export default async function CollectionPage({ params }: { params: Promise<{ collection: string }> }) {
+ const { collection: collectionSlug } = await params
+ const collection = getCollection(collectionSlug)
+
+ if (!collection) return notFound()
+
+ return (
+
+ {/* Breadcrumb */}
+
+
+
+
+ Home
+
+ {' '}/{' '}
+ {collection.title}
+
+
+
+
+ {/* Hero */}
+
+
+
+
+
+ {collection.title}
+
+
+ {collection.description}
+
+
+
+ {collection.status}
+
+
+
+
+
+ {/* Products */}
+
+
+
+ Products
+
+
+ {collection.products.map((product) => (
+
+
+
+ {product.name}
+
+ {product.badge && (
+
+ {product.badge}
+
+ )}
+
+
+ {product.tagline}
+
+
+ {product.price}
+
+
+ ))}
+
+
+
+
+ {/* Back */}
+
+
+
+ ← Back to all collections
+
+
+
+
+ )
+}
diff --git a/cdn/cms-bulk-redirects/app/globals.css b/cdn/cms-bulk-redirects/app/globals.css
new file mode 100644
index 0000000000..a2dc41ecee
--- /dev/null
+++ b/cdn/cms-bulk-redirects/app/globals.css
@@ -0,0 +1,26 @@
+@import "tailwindcss";
+
+:root {
+ --background: #ffffff;
+ --foreground: #171717;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+}
diff --git a/cdn/cms-bulk-redirects/app/layout.tsx b/cdn/cms-bulk-redirects/app/layout.tsx
new file mode 100644
index 0000000000..ca1f79a908
--- /dev/null
+++ b/cdn/cms-bulk-redirects/app/layout.tsx
@@ -0,0 +1,22 @@
+import type { Metadata } from 'next'
+import type { ReactNode } from 'react'
+import { GeistSans } from 'geist/font/sans'
+import { GeistMono } from 'geist/font/mono'
+import Navbar from '../components/Navbar'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'Essential Carry - Bulk Redirects Demo',
+ description: 'A catalog site showcasing Vercel bulk redirects with vercel.ts',
+}
+
+export default function RootLayout({ children }: { children: ReactNode }) {
+ return (
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/cdn/cms-bulk-redirects/app/not-found.tsx b/cdn/cms-bulk-redirects/app/not-found.tsx
new file mode 100644
index 0000000000..d6be63018b
--- /dev/null
+++ b/cdn/cms-bulk-redirects/app/not-found.tsx
@@ -0,0 +1,30 @@
+import Link from 'next/link'
+
+export default function NotFound() {
+ return (
+
+
+
+ 404
+
+
+ This page doesn't exist or has been moved.
+
+
+
+ Go home
+
+
+ Browse collections
+
+
+
+
+ )
+}
diff --git a/cdn/cms-bulk-redirects/app/page.tsx b/cdn/cms-bulk-redirects/app/page.tsx
new file mode 100644
index 0000000000..3ac58102e8
--- /dev/null
+++ b/cdn/cms-bulk-redirects/app/page.tsx
@@ -0,0 +1,152 @@
+import Link from 'next/link'
+
+export default function Home() {
+ return (
+
+ {/* Hero Section */}
+
+
+
+ Essential Carry
+
+
+ Thoughtfully designed everyday essentials for work, travel, and life.
+ Minimalist aesthetics meet maximum functionality.
+
+
+
+ Shop Fall Collection
+
+
+ View Latest
+
+
+
+
+
+ {/* Collections Grid */}
+
+
+
+
+ Shop by Season
+
+
+ Discover our curated collections, each designed for specific moments and seasons.
+
+
+
+
+ {/* Fall */}
+
+
+
+
🍂
+
Fall
+
+ Cozy layers and warm essentials for crisp autumn days
+
+
+
+ Shop Collection →
+
+
+
+
+ {/* Winter */}
+
+
+
+
❄️
+
Winter
+
+ Premium insulation and weather protection
+
+
+
+ Shop Collection →
+
+
+
+
+ {/* Spring */}
+
+
+
+
🌱
+
Spring 2026
+
+ Fresh starts with lightweight, versatile pieces
+
+
+
+ Preview Collection →
+
+
+
+
+ {/* Limited Edition */}
+
+
+
+
✨
+
Limited Edition
+
+ Exclusive collaborations and special releases
+
+
+
+ Explore →
+
+
+
+
+ {/* Archive */}
+
+
+
+
📦
+
Outlet
+
+ Past seasons at special prices
+
+
+
+ Browse Deals →
+
+
+
+
+
+
+
+ {/* Newsletter */}
+
+
+
+ Stay in the loop
+
+
+ Be the first to know about new collections and exclusive offers.
+
+
+
+
+ Subscribe
+
+
+
+
+
+ )
+}
diff --git a/cdn/cms-bulk-redirects/components/Navbar.tsx b/cdn/cms-bulk-redirects/components/Navbar.tsx
new file mode 100644
index 0000000000..411574870c
--- /dev/null
+++ b/cdn/cms-bulk-redirects/components/Navbar.tsx
@@ -0,0 +1,41 @@
+import Link from 'next/link'
+
+export default function Navbar() {
+ return (
+
+
+
+
+
+
+
+
Essential Carry
+
+
+
+
+
+
+ )
+}
diff --git a/cdn/cms-bulk-redirects/generated-redirects.json b/cdn/cms-bulk-redirects/generated-redirects.json
new file mode 100644
index 0000000000..4a1280dffe
--- /dev/null
+++ b/cdn/cms-bulk-redirects/generated-redirects.json
@@ -0,0 +1,34 @@
+[
+ {
+ "source": "/products/aurora-duffel",
+ "destination": "/catalog/limited-edition",
+ "statusCode": 302,
+ "query": true
+ },
+ {
+ "source": "/catalog/outlet",
+ "destination": "/catalog/archive",
+ "statusCode": 308,
+ "caseSensitive": false
+ },
+ {
+ "source": "/catalog/latest",
+ "destination": "/catalog/spring-2026",
+ "permanent": true
+ },
+ {
+ "source": "/products/daybreak-pack",
+ "destination": "/catalog/limited-edition",
+ "statusCode": 302,
+ "query": true
+ },
+ {
+ "source": "/catalog/winter",
+ "destination": "/catalog/winter-2025",
+ "permanent": true
+ },
+ {
+ "source": "/catalog/fall",
+ "destination": "/catalog/fall-2025"
+ }
+]
\ No newline at end of file
diff --git a/cdn/cms-bulk-redirects/lib/collections.ts b/cdn/cms-bulk-redirects/lib/collections.ts
new file mode 100644
index 0000000000..02725917a3
--- /dev/null
+++ b/cdn/cms-bulk-redirects/lib/collections.ts
@@ -0,0 +1,123 @@
+export type Product = {
+ name: string
+ price: string
+ tagline: string
+ badge?: string
+}
+
+export type Collection = {
+ slug: string
+ title: string
+ description: string
+ status: 'Live' | 'Pre-launch' | 'Limited' | 'Archived'
+ accent: string
+ incomingPaths: string[]
+ highlights: string[]
+ products: Product[]
+}
+
+export const collections: Collection[] = [
+ {
+ slug: 'fall-2025',
+ title: 'Fall 2025',
+ description:
+ 'Merino layers, structured carry, and weather-treated outerwear for a Northern-hemisphere launch.',
+ status: 'Live',
+ accent: 'from-amber-50 via-orange-50 to-amber-100',
+ incomingPaths: ['/catalog/fall', '/catalog/fall-2024'],
+ highlights: [
+ 'Redirects keep the retired /catalog/fall path pointed at the new season',
+ 'Merchandising tweaks publish in Contentful without shipping code',
+ 'Great for evergreen articles that always point to “fall”',
+ ],
+ products: [
+ { name: 'Transit Shell', price: '$198', tagline: 'Recycled, rain ready shell with heat mapping', badge: 'Featured' },
+ { name: 'Layered Merino', price: '$128', tagline: 'Temperature-regulating knit built for travel days' },
+ { name: 'Compression Pack', price: '$88', tagline: 'Carry-on sized with modular pouches' },
+ ],
+ },
+ {
+ slug: 'winter-2025',
+ title: 'Winter 2025',
+ description:
+ 'Thermal capsules and insulated accessories for cold-weather drops. Redirected from last year’s “winter” vanity URL.',
+ status: 'Live',
+ accent: 'from-slate-50 via-blue-50 to-indigo-100',
+ incomingPaths: ['/catalog/winter', '/catalog/winter-2024'],
+ highlights: [
+ 'Marketing keeps /catalog/winter alive while inventory rotates',
+ 'Preserves inbound traffic from ads without re-cutting creatives',
+ 'Simple CSV export from Contentful drives the bulk redirect file',
+ ],
+ products: [
+ { name: 'Glacier Parka', price: '$248', tagline: 'Storm-ready parka with recycled insulation', badge: 'New' },
+ { name: 'Thermal Bottle', price: '$38', tagline: 'All-day heat retention with a low-profile lid' },
+ { name: 'Cable Knit Beanie', price: '$34', tagline: 'Soft handfeel with traceable wool' },
+ ],
+ },
+ {
+ slug: 'spring-2026',
+ title: 'Spring 2026',
+ description:
+ 'Lightweight carry and breathable layers for the next launch wave. Great target for “/catalog/latest” redirects.',
+ status: 'Pre-launch',
+ accent: 'from-emerald-50 via-teal-50 to-emerald-100',
+ incomingPaths: ['/catalog/latest', '/campaign/spring-preview'],
+ highlights: [
+ 'Preview route collects RSVPs even before the PDPs are published',
+ 'Alias /catalog/latest always points to the newest collection',
+ 'Pairs well with geotargeted email deep links',
+ ],
+ products: [
+ { name: 'AirLight Shell', price: '$168', tagline: 'Packable windbreaker that folds into its own pocket' },
+ { name: 'Commuter Tote', price: '$118', tagline: 'Laptop-friendly tote with wet pocket' },
+ { name: 'Everyday Sandal', price: '$78', tagline: 'Minimal silhouette with soft webbing' },
+ ],
+ },
+ {
+ slug: 'limited-edition',
+ title: 'Limited Edition',
+ description:
+ 'Short-run collabs and “small batch” drops. Redirect product aliases to this landing page when a SKU sunsets.',
+ status: 'Limited',
+ accent: 'from-fuchsia-50 via-rose-50 to-purple-100',
+ incomingPaths: ['/products/daybreak-pack', '/products/aurora-duffel'],
+ highlights: [
+ 'Great destination for influencer vanity URLs',
+ 'Surface “back in stock” signups before the PDP reactivates',
+ 'Redirect individual SKUs instead of the whole catalog',
+ ],
+ products: [
+ { name: 'Daybreak Pack', price: '$168', tagline: 'Structured EDC pack with magnetic hardware', badge: 'Backorder' },
+ { name: 'Aurora Duffel', price: '$198', tagline: 'Weekender with compression straps' },
+ { name: 'Studio Sling', price: '$98', tagline: 'Hands-free sling with hidden laptop sleeve' },
+ ],
+ },
+ {
+ slug: 'archive',
+ title: 'Archive & Outlet',
+ description:
+ 'Keep revenue flowing from evergreen blog links by routing retired SKUs to the archive instead of 404s.',
+ status: 'Archived',
+ accent: 'from-amber-50 via-slate-50 to-slate-100',
+ incomingPaths: ['/catalog/outlet', '/catalog/archive'],
+ highlights: [
+ 'Preserve SEO equity for long-lived merch articles',
+ 'Contentful entry controls whether we use 308 or 301 per path',
+ 'Pair with analytics to see how often legacy URLs are hit',
+ ],
+ products: [
+ { name: 'Sample Sale Kits', price: '$58', tagline: 'Assorted pulls from last season' },
+ { name: 'Seconds Bin', price: '$24', tagline: 'Minor cosmetic blemishes, full warranty' },
+ { name: 'Gift with Purchase', price: '$0', tagline: 'Add-on items surfaced via redirect' },
+ ],
+ },
+]
+
+export function getCollection(slug: string): Collection | undefined {
+ return collections.find((collection) => collection.slug === slug)
+}
+
+export function getCollectionParams() {
+ return collections.map((collection) => ({ collection: collection.slug }))
+}
diff --git a/cdn/cms-bulk-redirects/package.json b/cdn/cms-bulk-redirects/package.json
new file mode 100644
index 0000000000..9033e1bb73
--- /dev/null
+++ b/cdn/cms-bulk-redirects/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "cms-bulk-redirects",
+ "version": "1.0.0",
+ "private": true,
+ "repository": "https://github.com/vercel/examples.git",
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@vercel/config": "^0.0.22",
+ "geist": "^1.5.1",
+ "next": "^16.0.7",
+ "react": "^19.2.1",
+ "react-dom": "^19.2.1"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "tailwindcss": "^4",
+ "typescript": "^5"
+ },
+ "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a"
+}
diff --git a/cdn/cms-bulk-redirects/pnpm-lock.yaml b/cdn/cms-bulk-redirects/pnpm-lock.yaml
new file mode 100644
index 0000000000..af48c79670
--- /dev/null
+++ b/cdn/cms-bulk-redirects/pnpm-lock.yaml
@@ -0,0 +1,992 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@vercel/config':
+ specifier: ^0.0.22
+ version: 0.0.22
+ geist:
+ specifier: ^1.5.1
+ version: 1.5.1(next@16.0.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1))
+ next:
+ specifier: ^16.0.7
+ version: 16.0.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+ react:
+ specifier: ^19.2.1
+ version: 19.2.1
+ react-dom:
+ specifier: ^19.2.1
+ version: 19.2.1(react@19.2.1)
+ devDependencies:
+ '@tailwindcss/postcss':
+ specifier: ^4
+ version: 4.1.17
+ '@types/node':
+ specifier: ^20
+ version: 20.19.26
+ '@types/react':
+ specifier: ^19
+ version: 19.2.7
+ '@types/react-dom':
+ specifier: ^19
+ version: 19.2.3(@types/react@19.2.7)
+ tailwindcss:
+ specifier: ^4
+ version: 4.1.17
+ typescript:
+ specifier: ^5
+ version: 5.9.3
+
+packages:
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@emnapi/runtime@1.7.1':
+ resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
+
+ '@img/colour@1.0.0':
+ resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
+ 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]
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@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]
+
+ '@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]
+
+ '@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]
+
+ '@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]
+
+ '@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]
+
+ '@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]
+
+ '@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]
+
+ '@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]
+
+ '@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]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@next/env@16.0.8':
+ resolution: {integrity: sha512-xP4WrQZuj9MdmLJy3eWFHepo+R3vznsMSS8Dy3wdA7FKpjCiesQ6DxZvdGziQisj0tEtCgBKJzjcAc4yZOgLEQ==}
+
+ '@next/swc-darwin-arm64@16.0.8':
+ resolution: {integrity: sha512-yjVMvTQN21ZHOclQnhSFbjBTEizle+1uo4NV6L4rtS9WO3nfjaeJYw+H91G+nEf3Ef43TaEZvY5mPWfB/De7tA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@16.0.8':
+ resolution: {integrity: sha512-+zu2N3QQ0ZOb6RyqQKfcu/pn0UPGmg+mUDqpAAEviAcEVEYgDckemOpiMRsBP3IsEKpcoKuNzekDcPczEeEIzA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@16.0.8':
+ resolution: {integrity: sha512-LConttk+BeD0e6RG0jGEP9GfvdaBVMYsLJ5aDDweKiJVVCu6sGvo+Ohz9nQhvj7EQDVVRJMCGhl19DmJwGr6bQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@16.0.8':
+ resolution: {integrity: sha512-JaXFAlqn8fJV+GhhA9lpg6da/NCN/v9ub98n3HoayoUSPOVdoxEEt86iT58jXqQCs/R3dv5ZnxGkW8aF4obMrQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@16.0.8':
+ resolution: {integrity: sha512-O7M9it6HyNhsJp3HNAsJoHk5BUsfj7hRshfptpGcVsPZ1u0KQ/oVy8oxF7tlwxA5tR43VUP0yRmAGm1us514ng==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@16.0.8':
+ resolution: {integrity: sha512-8+KClEC/GLI2dLYcrWwHu5JyC5cZYCFnccVIvmxpo6K+XQt4qzqM5L4coofNDZYkct/VCCyJWGbZZDsg6w6LFA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@16.0.8':
+ resolution: {integrity: sha512-rpQ/PgTEgH68SiXmhu/cJ2hk9aZ6YgFvspzQWe2I9HufY6g7V02DXRr/xrVqOaKm2lenBFPNQ+KAaeveywqV+A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@16.0.8':
+ resolution: {integrity: sha512-jWpWjWcMQu2iZz4pEK2IktcfR+OA9+cCG8zenyLpcW8rN4rzjfOzH4yj/b1FiEAZHKS+5Vq8+bZyHi+2yqHbFA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tailwindcss/node@4.1.17':
+ resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.17':
+ resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.17':
+ resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.17':
+ resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.17':
+ resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
+ resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
+ resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.17':
+ resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.17':
+ resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.17':
+ resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.17':
+ resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
+ resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.17':
+ resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.17':
+ resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.17':
+ resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==}
+
+ '@types/node@20.19.26':
+ resolution: {integrity: sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.7':
+ resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
+
+ '@vercel/config@0.0.22':
+ resolution: {integrity: sha512-bh2x7Ex1mm97LG+GAna9wpKcgo3+p19GOrW0ZgNgC6k0ys4Y4+M/8gxhhszT3wo/KtV0hznqJW01lQrVFu1Rlw==}
+ hasBin: true
+
+ caniuse-lite@1.0.30001759:
+ resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==}
+
+ 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'}
+
+ enhanced-resolve@5.18.3:
+ resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
+ engines: {node: '>=10.13.0'}
+
+ geist@1.5.1:
+ resolution: {integrity: sha512-mAHZxIsL2o3ZITFaBVFBnwyDOw+zNLYum6A6nIjpzCGIO8QtC3V76XF2RnZTyLx1wlDTmMDy8jg3Ib52MIjGvQ==}
+ peerDependencies:
+ next: '>=13.2.0'
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ lightningcss-android-arm64@1.30.2:
+ resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.30.2:
+ resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.2:
+ resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.2:
+ resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.2:
+ resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
+ engines: {node: '>= 12.0.0'}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ next@16.0.8:
+ resolution: {integrity: sha512-LmcZzG04JuzNXi48s5P+TnJBsTGPJunViNKV/iE4uM6kstjTQsQhvsAv+xF6MJxU2Pr26tl15eVbp0jQnsv6/g==}
+ engines: {node: '>=20.9.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
+
+ 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}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ pretty-cache-header@1.0.0:
+ resolution: {integrity: sha512-xtXazslu25CdnGnUkByU1RoOjK55TqwatJkjjJLg5ZAdz2Lngko/mmaUgeET36P2GMlNwh3fdM7FWBO717pNcw==}
+ engines: {node: '>=12.13'}
+
+ react-dom@19.2.1:
+ resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==}
+ peerDependencies:
+ react: ^19.2.1
+
+ react@19.2.1:
+ resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==}
+ engines: {node: '>=0.10.0'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@7.7.3:
+ resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
+ 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'}
+
+ 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
+
+ tailwindcss@4.1.17:
+ resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ timestring@6.0.0:
+ resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==}
+ engines: {node: '>=8'}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ 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==}
+
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+
+snapshots:
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@emnapi/runtime@1.7.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@img/colour@1.0.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.7.1
+ 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
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@next/env@16.0.8': {}
+
+ '@next/swc-darwin-arm64@16.0.8':
+ optional: true
+
+ '@next/swc-darwin-x64@16.0.8':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@16.0.8':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@16.0.8':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@16.0.8':
+ optional: true
+
+ '@next/swc-linux-x64-musl@16.0.8':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@16.0.8':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@16.0.8':
+ optional: true
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.1.17':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.17
+
+ '@tailwindcss/oxide-android-arm64@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.17':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.17':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.17
+ '@tailwindcss/oxide-darwin-arm64': 4.1.17
+ '@tailwindcss/oxide-darwin-x64': 4.1.17
+ '@tailwindcss/oxide-freebsd-x64': 4.1.17
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.17
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.17
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.17
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.17
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.17
+
+ '@tailwindcss/postcss@4.1.17':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.17
+ '@tailwindcss/oxide': 4.1.17
+ postcss: 8.5.6
+ tailwindcss: 4.1.17
+
+ '@types/node@20.19.26':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/react-dom@19.2.3(@types/react@19.2.7)':
+ dependencies:
+ '@types/react': 19.2.7
+
+ '@types/react@19.2.7':
+ dependencies:
+ csstype: 3.2.3
+
+ '@vercel/config@0.0.22':
+ dependencies:
+ pretty-cache-header: 1.0.0
+ zod: 3.25.76
+
+ caniuse-lite@1.0.30001759: {}
+
+ client-only@0.0.1: {}
+
+ csstype@3.2.3: {}
+
+ detect-libc@2.1.2: {}
+
+ enhanced-resolve@5.18.3:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ geist@1.5.1(next@16.0.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)):
+ dependencies:
+ next: 16.0.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
+
+ graceful-fs@4.2.11: {}
+
+ jiti@2.6.1: {}
+
+ lightningcss-android-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.2:
+ optional: true
+
+ lightningcss@1.30.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.30.2
+ lightningcss-darwin-arm64: 1.30.2
+ lightningcss-darwin-x64: 1.30.2
+ lightningcss-freebsd-x64: 1.30.2
+ lightningcss-linux-arm-gnueabihf: 1.30.2
+ lightningcss-linux-arm64-gnu: 1.30.2
+ lightningcss-linux-arm64-musl: 1.30.2
+ lightningcss-linux-x64-gnu: 1.30.2
+ lightningcss-linux-x64-musl: 1.30.2
+ lightningcss-win32-arm64-msvc: 1.30.2
+ lightningcss-win32-x64-msvc: 1.30.2
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ nanoid@3.3.11: {}
+
+ next@16.0.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
+ dependencies:
+ '@next/env': 16.0.8
+ '@swc/helpers': 0.5.15
+ caniuse-lite: 1.0.30001759
+ postcss: 8.4.31
+ react: 19.2.1
+ react-dom: 19.2.1(react@19.2.1)
+ styled-jsx: 5.1.6(react@19.2.1)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 16.0.8
+ '@next/swc-darwin-x64': 16.0.8
+ '@next/swc-linux-arm64-gnu': 16.0.8
+ '@next/swc-linux-arm64-musl': 16.0.8
+ '@next/swc-linux-x64-gnu': 16.0.8
+ '@next/swc-linux-x64-musl': 16.0.8
+ '@next/swc-win32-arm64-msvc': 16.0.8
+ '@next/swc-win32-x64-msvc': 16.0.8
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ picocolors@1.1.1: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ pretty-cache-header@1.0.0:
+ dependencies:
+ timestring: 6.0.0
+
+ react-dom@19.2.1(react@19.2.1):
+ dependencies:
+ react: 19.2.1
+ scheduler: 0.27.0
+
+ react@19.2.1: {}
+
+ scheduler@0.27.0: {}
+
+ semver@7.7.3:
+ optional: true
+
+ 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
+ optional: true
+
+ source-map-js@1.2.1: {}
+
+ styled-jsx@5.1.6(react@19.2.1):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.1
+
+ tailwindcss@4.1.17: {}
+
+ tapable@2.3.0: {}
+
+ timestring@6.0.0: {}
+
+ tslib@2.8.1: {}
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
+
+ zod@3.25.76: {}
diff --git a/cdn/cms-bulk-redirects/postcss.config.mjs b/cdn/cms-bulk-redirects/postcss.config.mjs
new file mode 100644
index 0000000000..c7bcb4b1ee
--- /dev/null
+++ b/cdn/cms-bulk-redirects/postcss.config.mjs
@@ -0,0 +1,5 @@
+const config = {
+ plugins: ["@tailwindcss/postcss"],
+};
+
+export default config;
diff --git a/cdn/cms-bulk-redirects/public/favicon.ico b/cdn/cms-bulk-redirects/public/favicon.ico
new file mode 100644
index 0000000000..4ddd8fff70
Binary files /dev/null and b/cdn/cms-bulk-redirects/public/favicon.ico differ
diff --git a/cdn/cms-bulk-redirects/tsconfig.json b/cdn/cms-bulk-redirects/tsconfig.json
new file mode 100644
index 0000000000..a9f83b8ba3
--- /dev/null
+++ b/cdn/cms-bulk-redirects/tsconfig.json
@@ -0,0 +1,37 @@
+{
+ "compilerOptions": {
+ "target": "es2020",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/cdn/cms-bulk-redirects/vercel.ts b/cdn/cms-bulk-redirects/vercel.ts
new file mode 100644
index 0000000000..b9b7f5d5aa
--- /dev/null
+++ b/cdn/cms-bulk-redirects/vercel.ts
@@ -0,0 +1,93 @@
+import type { VercelConfig } from '@vercel/config/v1'
+import { writeFileSync } from 'fs'
+import { join } from 'path'
+
+type VercelRedirect = {
+ source: string
+ destination: string
+ permanent?: boolean
+ statusCode?: number
+ caseSensitive?: boolean
+ query?: boolean
+}
+
+type ContentfulEntry = {
+ fields: Record
+}
+
+type ContentfulResponse = {
+ items?: ContentfulEntry[]
+}
+
+const fallbackRedirects: VercelRedirect[] = [
+ { source: '/catalog/fall', destination: '/catalog/fall-2025', statusCode: 302 },
+ { source: '/catalog/winter', destination: '/catalog/winter-2025', permanent: true },
+ { source: '/catalog/latest', destination: '/catalog/spring-2026', permanent: true },
+ { source: '/products/daybreak-pack', destination: '/catalog/limited-edition', statusCode: 302, query: true },
+ { source: '/catalog/outlet', destination: '/catalog/archive', statusCode: 308, caseSensitive: false },
+]
+
+const normalizePath = (path: string) => (path.startsWith('/') ? path : `/${path}`)
+
+async function fetchContentfulRedirects(): Promise {
+ console.log('fetching contentful redirects')
+ const spaceId = process.env.CONTENTFUL_SPACE_ID
+ const accessToken = process.env.CONTENTFUL_ACCESS_TOKEN
+
+ if (!spaceId || !accessToken) {
+ console.warn('⚠️ Skipping Contentful sync: set CONTENTFUL_SPACE_ID and CONTENTFUL_ACCESS_TOKEN to pull CMS redirects')
+ return null
+ }
+
+ const url = new URL(`https://cdn.contentful.com/spaces/${spaceId}/entries`)
+ url.searchParams.set('content_type', 'redirect')
+ url.searchParams.set('access_token', accessToken)
+ url.searchParams.set('limit', '1000')
+
+ const response = await fetch(url.toString())
+
+ if (!response.ok) {
+ throw new Error(`Contentful API error: ${response.status} ${response.statusText}`)
+ }
+
+ const data = (await response.json()) as ContentfulResponse
+ const entries = data.items ?? []
+
+ if (entries.length === 0) {
+ console.info('ℹ️ No redirects returned by Contentful')
+ return []
+ }
+
+ return entries.map((entry) => {
+ const fields = entry.fields
+ const redirect: VercelRedirect = {
+ source: normalizePath(fields.source),
+ destination: normalizePath(fields.destination),
+ }
+
+ if (fields.statusCode !== undefined) redirect.statusCode = Number(fields.statusCode)
+ if (fields.permanent !== undefined) redirect.permanent = Boolean(fields.permanent)
+ if (fields.caseSensitive !== undefined) redirect.caseSensitive = Boolean(fields.caseSensitive)
+ if (fields.preserveQuery !== undefined) redirect.query = Boolean(fields.preserveQuery)
+
+ return redirect
+ })
+}
+
+const redirectsFromContentful = await fetchContentfulRedirects()
+const redirectsToWrite =
+ redirectsFromContentful === null
+ ? fallbackRedirects
+ : redirectsFromContentful.length > 0
+ ? redirectsFromContentful
+ : fallbackRedirects
+
+const redirectsPath = join(process.cwd(), 'generated-redirects.json')
+writeFileSync(redirectsPath, JSON.stringify(redirectsToWrite, null, 2))
+console.log(`✓ Bulk redirects ready (${redirectsToWrite.length} rules) -> ${redirectsPath}`)
+
+export const config: VercelConfig = {
+ framework: 'nextjs',
+ outputDirectory: '.next',
+ bulkRedirectsPath: './generated-redirects.json',
+}