diff --git a/.cursor/rules/snyk_rules.mdc b/.cursor/rules/snyk_rules.mdc
new file mode 100644
index 0000000..eef0634
--- /dev/null
+++ b/.cursor/rules/snyk_rules.mdc
@@ -0,0 +1,14 @@
+---
+alwaysApply: true
+always_on: true
+trigger: always_on
+applyTo: "**"
+description: Snyk Security At Inception
+---
+
+# Project security best practices
+
+- Always run snyk_code_scan tool for new first party code that is generated in a Snyk-supported language.
+- If any security issues are found based on newly introduced or modified code or dependencies, attempt to fix the issues using the results context from Snyk.
+- Rescan the code after fixing the issues to ensure that the issues were fixed and that there are no newly introduced issues.
+- Repeat this process until no new issues are found.
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..af53d91
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+.git
+.next
+node_modules
+Dockerfile
+docker-compose.yml
+README.md
+.env*
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5ef6a52..98b06b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,9 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
+# docker override files with real credentials
+docker-compose.override.yml
+
# vercel
.vercel
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..4fb0182
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,8 @@
+{
+ "i18n-ally.localesPaths": [
+ "messages",
+ "src/i18n"
+ ],
+ "snyk.advanced.organization": "512ef4a1-6034-4537-a391-9692d282122a",
+ "snyk.advanced.autoSelectOrganization": true
+}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..0e87835
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,51 @@
+# syntax=docker/dockerfile:1
+# Stage 1: Dependencies
+FROM --platform=$BUILDPLATFORM node:20-alpine AS deps
+WORKDIR /app
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+COPY package.json pnpm-lock.yaml ./
+RUN pnpm install --frozen-lockfile
+
+# Stage 2: Builder
+FROM --platform=$BUILDPLATFORM node:20-alpine AS builder
+WORKDIR /app
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@latest --activate
+
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+ENV NEXT_TELEMETRY_DISABLED=1
+
+RUN pnpm run build
+
+# Stage 3: Runner
+FROM node:20-alpine AS runner
+WORKDIR /app
+
+ENV NODE_ENV=production
+ENV NEXT_TELEMETRY_DISABLED=1
+
+RUN addgroup --system --gid 1001 nodejs
+RUN adduser --system --uid 1001 nextjs
+
+# Copy only necessary files
+COPY --from=builder /app/public ./public
+COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
+COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+
+# Copy tenant data and message files (not included in standalone bundle)
+COPY --from=builder --chown=nextjs:nodejs /app/data ./data
+COPY --from=builder --chown=nextjs:nodejs /app/messages ./messages
+
+USER nextjs
+
+EXPOSE 3000
+
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
+
+CMD ["node", "server.js"]
\ No newline at end of file
diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md
new file mode 100644
index 0000000..8285d8a
--- /dev/null
+++ b/KNOWN_ISSUES.md
@@ -0,0 +1,38 @@
+# Known Issues
+
+## OpenGraph Images Removed
+
+**Issue:** OpenGraph image tags have been intentionally removed from all metadata.
+
+**Why:** Next.js static generation + Docker is fundamentally broken for OpenGraph images.
+
+**The Problem:**
+
+- `NEXT_PUBLIC_*` variables are embedded at **build time**, not runtime
+- Static pages get URLs baked in permanently during Docker build
+- No way to change URLs after build without rebuilding entire image
+- `metadataBase` doesn't work in Docker builds (only works with `npm run build && npm run start`)
+- Results in hardcoded `localhost:3000` URLs in production
+
+**Next.js Design Flaws:**
+
+- Treats `NEXT_PUBLIC_*` as "client-side" but uses them for server-side metadata generation
+- Static generation breaks basic containerization patterns
+- Framework optimized for Vercel's walled garden, not real-world Docker deployments
+- No proper runtime environment variable support for metadata
+
+**Attempted Solutions (All Failed):**
+
+- Runtime header detection → breaks static generation
+- Dynamic rendering → metadata renders in `
` instead of ``
+- Build arguments → breaks universal Docker distribution
+- Placeholder replacement → hacky and unreliable
+
+**Current Solution:**
+Removed OpenGraph images entirely. All other metadata (title, description, etc.) works perfectly and provides excellent social media previews without the architectural nightmare.
+
+**Impact:**
+Social platforms still show beautiful previews with title/description. Only missing the large preview image, which isn't worth the engineering complexity.
+
+**Recommendation:**
+Don't use Next.js for projects requiring universal Docker distribution with OpenGraph images. Consider SvelteKit, Nuxt, or Remix instead.
diff --git a/data/tenants.json b/data/tenants.json
new file mode 100644
index 0000000..c59e666
--- /dev/null
+++ b/data/tenants.json
@@ -0,0 +1,7 @@
+{
+ "gallery.nini.love": "nini",
+ "nini.boger.dev": "nini",
+ "alice.boger.dev": "alice",
+ "localhost:3000": "nini",
+ "localhost": "nini"
+}
diff --git a/data/tenants/alice/artist-translations.json b/data/tenants/alice/artist-translations.json
new file mode 100644
index 0000000..5de9b58
--- /dev/null
+++ b/data/tenants/alice/artist-translations.json
@@ -0,0 +1,22 @@
+{
+ "de": {
+ "name": "Alice",
+ "description": "Eine digitale Künstlerin und Illustratorin, die fantastische Welten zum Leben erweckt. Ich lasse mich von Märchen, Natur und dem Surrealen inspirieren. Meine Arbeit verbindet lebhafte Farben mit traumhaften Kompositionen — jedes Stück ist ein Tor in eine andere Realität."
+ },
+ "es": {
+ "name": "Alice",
+ "description": "Una artista digital e ilustradora que da vida a mundos fantásticos. Me inspiro en los cuentos de hadas, la naturaleza y lo surreal. Mi trabajo combina colores vivos con composiciones oníricas — cada pieza es una puerta a otra realidad."
+ },
+ "ka": {
+ "name": "ალისი",
+ "description": "ციფრული მხატვარი და ილუსტრატორი, რომელიც ფანტასტიკურ სამყაროებს აცოცხლებს. ზღაპრები, ბუნება და სიურეალიზმი შთამაგონებს. ჩემი ნამუშევრები ცოცხალ ფერებსა და ოცნებისებურ კომპოზიციებს აერთიანებს — ყოველი ნამუშევარი სხვა რეალობაში შესასვლელია."
+ },
+ "ru": {
+ "name": "Алиса",
+ "description": "Цифровой художник и иллюстратор, оживляющая фантастические миры. Я черпаю вдохновение из сказок, природы и сюрреализма. Моя работа сочетает яркие цвета с мечтательными композициями — каждое произведение — это дверь в другую реальность."
+ },
+ "tr": {
+ "name": "Alice",
+ "description": "Fantastik dünyaları hayata geçiren dijital bir sanatçı ve illüstratör. Peri masallarından, doğadan ve gerçeküstünden ilham alıyorum. Çalışmalarım canlı renkleri rüya gibi kompozisyonlarla birleştiriyor — her parça başka bir gerçekliğe açılan bir kapı."
+ }
+}
diff --git a/data/tenants/alice/artist.json b/data/tenants/alice/artist.json
new file mode 100644
index 0000000..a1c00be
--- /dev/null
+++ b/data/tenants/alice/artist.json
@@ -0,0 +1,5 @@
+{
+ "name": "Alice",
+ "description": "A digital artist and illustrator who brings fantastical worlds to life. I draw inspiration from fairy tales, nature, and the surreal. My work blends vivid colors with dreamlike compositions — each piece is a doorway into another reality.",
+ "defaultLanguage": "en"
+}
diff --git a/data/tenants/alice/artworks-base.json b/data/tenants/alice/artworks-base.json
new file mode 100644
index 0000000..4f9d947
--- /dev/null
+++ b/data/tenants/alice/artworks-base.json
@@ -0,0 +1,54 @@
+{
+ "categorySections": [
+ {
+ "id": "paintings",
+ "artworks": [
+ {
+ "id": "p1",
+ "imageUrl": "#6A5ACD",
+ "category": "paintings",
+ "mediumKey": "digitalPainting",
+ "dimensions": "3000 x 4000 px",
+ "year": 2025
+ },
+ {
+ "id": "p2",
+ "imageUrl": "#20B2AA",
+ "category": "paintings",
+ "mediumKey": "watercolorOnPaper",
+ "dimensions": "18\" x 24\"",
+ "year": 2025
+ },
+ {
+ "id": "p3",
+ "imageUrl": "#FF6347",
+ "category": "paintings",
+ "mediumKey": "digitalPainting",
+ "dimensions": "4000 x 3000 px",
+ "year": 2024
+ }
+ ]
+ },
+ {
+ "id": "origami",
+ "artworks": [
+ {
+ "id": "o1",
+ "imageUrl": "#DA70D6",
+ "category": "origami",
+ "mediumKey": "watercolorOnPaper",
+ "dimensions": "12\" x 16\"",
+ "year": 2025
+ },
+ {
+ "id": "o2",
+ "imageUrl": "#3CB371",
+ "category": "origami",
+ "mediumKey": "inkOnPaper",
+ "dimensions": "9\" x 12\"",
+ "year": 2024
+ }
+ ]
+ }
+ ]
+}
diff --git a/data/tenants/alice/personal-message.json b/data/tenants/alice/personal-message.json
new file mode 100644
index 0000000..161d2cd
--- /dev/null
+++ b/data/tenants/alice/personal-message.json
@@ -0,0 +1,7 @@
+{
+ "enabled": false,
+ "recipient": "",
+ "message": "",
+ "dismissible": true,
+ "ariaLabel": ""
+}
diff --git a/data/tenants/alice/tags.json b/data/tenants/alice/tags.json
new file mode 100644
index 0000000..4dac582
--- /dev/null
+++ b/data/tenants/alice/tags.json
@@ -0,0 +1,11 @@
+{
+ "siteKeywords": [
+ "art gallery",
+ "digital art",
+ "illustration",
+ "fantasy art",
+ "watercolor",
+ "surreal",
+ "prints"
+ ]
+}
diff --git a/data/tenants/nini/artist-translations.json b/data/tenants/nini/artist-translations.json
new file mode 100644
index 0000000..8c2cd1b
--- /dev/null
+++ b/data/tenants/nini/artist-translations.json
@@ -0,0 +1,22 @@
+{
+ "de": {
+ "name": "Nini",
+ "description": "Ein süßes georgisches Mädchen mit einer Leidenschaft für Kunst und Design. Ich liebe es, meine Kreativität durch verschiedene Medien auszudrücken. Jedes Stück, das ich erschaffe, trägt ein Stück meiner Seele und meiner Heimat in sich. Wenn ich nicht gerade etwas Neues erschaffe, träume ich von meinem nächsten künstlerischen Abenteuer! ✨"
+ },
+ "es": {
+ "name": "Nini",
+ "description": "Una linda chica georgiana con pasión por el arte y el diseño. Me encanta expresar mi creatividad a través de diferentes medios. Cada pieza que creo lleva un pedacito de mi alma y mi tierra natal. Cuando no estoy creando algo nuevo, puedes encontrarme soñando con mi próxima aventura artística! ✨"
+ },
+ "ka": {
+ "name": "ნინი",
+ "description": "ლამაზი ქართველი გოგონა, რომელსაც ხელოვნებისა და დიზაინის მიმართ ვნება აქვს. მიყვარს ჩემი შემოქმედებითობის გამოხატვა სხვადასხვა საშუალებებით. ყოველი ნამუშევარი, რომელსაც ვქმნი, ჩემი სულისა და ჩემი სამშობლოს ნაწილს შეიცავს. როცა რაღაც ახალს არ ვქმნი, ჩემს შემდეგ ხელოვნურ თავგადასავალზე ვოცნებობ! ✨"
+ },
+ "ru": {
+ "name": "Нини",
+ "description": "Милая грузинская девушка с страстью к искусству и дизайну. Я люблю выражать свое творчество через различные материалы. Каждое произведение, которое я создаю, несет частичку моей души и моей родины. Когда я не создаю что-то новое, вы можете найти меня мечтающей о моем следующем художественном приключении! ✨"
+ },
+ "tr": {
+ "name": "Nini",
+ "description": "Sanat ve tasarıma tutkuyla bağlı sevimli bir Gürcü kız. Yaratıcılığımı farklı ortamlar aracılığıyla ifade etmeyi seviyorum. Yarattığım her eser, ruhumun ve vatanımın bir parçasını taşır. Yeni bir şey yaratmadığım zamanlarda, bir sonraki sanatsal maceramı hayal ediyorum! ✨"
+ }
+}
diff --git a/data/tenants/nini/artist.json b/data/tenants/nini/artist.json
new file mode 100644
index 0000000..4e88b62
--- /dev/null
+++ b/data/tenants/nini/artist.json
@@ -0,0 +1,5 @@
+{
+ "name": "Nini",
+ "description": "A cute Georgian girl with a passion for art and design. I love expressing my creativity through different mediums. Each piece I create carries a bit of my soul and my homeland. When I'm not crafting something new, you can find me dreaming up my next artistic adventure! ✨",
+ "defaultLanguage": "en"
+}
diff --git a/data/tenants/nini/artworks-base.json b/data/tenants/nini/artworks-base.json
new file mode 100644
index 0000000..181155d
--- /dev/null
+++ b/data/tenants/nini/artworks-base.json
@@ -0,0 +1,160 @@
+{
+ "categorySections": [
+ {
+ "id": "origami",
+ "artworks": [
+ {
+ "id": "o1",
+ "imageUrl": "#FF9F9F",
+ "category": "origami",
+ "mediumKey": "washiPaper",
+ "dimensions": "6\" x 6\"",
+ "year": 2024
+ },
+ {
+ "id": "o2",
+ "imageUrl": "#FFB5B5",
+ "category": "origami",
+ "mediumKey": "origamiPaper",
+ "dimensions": "8\" x 8\"",
+ "year": 2024
+ },
+ {
+ "id": "o3",
+ "imageUrl": "#98FB98",
+ "category": "origami",
+ "mediumKey": "origamiPaper",
+ "dimensions": "4\" x 4\"",
+ "year": 2024
+ },
+ {
+ "id": "o4",
+ "imageUrl": "#87CEEB",
+ "category": "origami",
+ "mediumKey": "specialtyOrigamiPaper",
+ "dimensions": "5\" x 5\" x 5\"",
+ "year": 2024
+ },
+ {
+ "id": "o5",
+ "imageUrl": "#FFB6C1",
+ "category": "origami",
+ "mediumKey": "origamiPaper",
+ "dimensions": "6\" x 6\"",
+ "year": 2024
+ }
+ ]
+ },
+ {
+ "id": "crochet",
+ "artworks": [
+ {
+ "id": "c1",
+ "imageUrl": "#95E1D3",
+ "category": "crochet",
+ "mediumKey": "merinoWool",
+ "dimensions": "4\" height",
+ "year": 2024
+ },
+ {
+ "id": "c2",
+ "imageUrl": "#A8E6CF",
+ "category": "crochet",
+ "mediumKey": "cottonYarn",
+ "dimensions": "50\" x 60\"",
+ "year": 2023
+ },
+ {
+ "id": "c3",
+ "imageUrl": "#90EE90",
+ "category": "crochet",
+ "mediumKey": "cottonYarn",
+ "dimensions": "6\" height",
+ "year": 2024
+ },
+ {
+ "id": "c4",
+ "imageUrl": "#FFF0F5",
+ "category": "crochet",
+ "mediumKey": "softAcrylicYarn",
+ "dimensions": "8\" height",
+ "year": 2024
+ },
+ {
+ "id": "c5",
+ "imageUrl": "#E6E6FA",
+ "category": "crochet",
+ "mediumKey": "softAcrylicYarn",
+ "dimensions": "7\" height",
+ "year": 2024
+ },
+ {
+ "id": "c6",
+ "imageUrl": "#8B4513",
+ "category": "crochet",
+ "mediumKey": "cottonYarn",
+ "dimensions": "12\" height",
+ "year": 2024
+ }
+ ]
+ },
+ {
+ "id": "paintings",
+ "artworks": [
+ {
+ "id": "p1",
+ "imageUrl": "#FFD93D",
+ "category": "paintings",
+ "mediumKey": "acrylicOnCanvas",
+ "dimensions": "24\" x 36\"",
+ "year": 2024
+ },
+ {
+ "id": "p2",
+ "imageUrl": "#F6CD61",
+ "category": "paintings",
+ "mediumKey": "oilOnCanvas",
+ "dimensions": "30\" x 40\"",
+ "year": 2023
+ },
+ {
+ "id": "p3",
+ "imageUrl": "#DDA0DD",
+ "category": "paintings",
+ "mediumKey": "mixedMediaOnCanvas",
+ "dimensions": "36\" x 48\"",
+ "year": 2024
+ }
+ ]
+ },
+ {
+ "id": "fingernails",
+ "artworks": [
+ {
+ "id": "n1",
+ "imageUrl": "#FF8B94",
+ "category": "fingernails",
+ "mediumKey": "gelPolish",
+ "dimensions": "Standard nail size",
+ "year": 2024
+ },
+ {
+ "id": "n2",
+ "imageUrl": "#FFA69E",
+ "category": "fingernails",
+ "mediumKey": "regularPolish",
+ "dimensions": "Standard nail size",
+ "year": 2024
+ },
+ {
+ "id": "n3",
+ "imageUrl": "#FF69B4",
+ "category": "fingernails",
+ "mediumKey": "gelPolish",
+ "dimensions": "Standard nail size",
+ "year": 2024
+ }
+ ]
+ }
+ ]
+}
diff --git a/data/tenants/nini/personal-message.json b/data/tenants/nini/personal-message.json
new file mode 100644
index 0000000..17ded0d
--- /dev/null
+++ b/data/tenants/nini/personal-message.json
@@ -0,0 +1,7 @@
+{
+ "enabled": true,
+ "recipient": "To my Tibik!",
+ "message": "Every color I pick has your smile in it. Every origami fold holds our hugs. I put pieces of our love in each artwork - so everyone can see you belong to me! Your support makes my hands create better, but these creations are just to keep you closer. No one else can inspire my art like you do. No one else can have your hugs or see your special smile. You are my art, my inspiration, my everything. No running away from my gallery! ❤️",
+ "dismissible": true,
+ "ariaLabel": "Personal message for To my Tibik!"
+}
diff --git a/data/tenants/nini/tags.json b/data/tenants/nini/tags.json
new file mode 100644
index 0000000..e219475
--- /dev/null
+++ b/data/tenants/nini/tags.json
@@ -0,0 +1,11 @@
+{
+ "siteKeywords": [
+ "art gallery",
+ "origami",
+ "crochet",
+ "paintings",
+ "nail art",
+ "handmade",
+ "crafts"
+ ]
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..5cb892f
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,50 @@
+services:
+ web:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ platforms:
+ - linux/amd64
+ - linux/arm64
+ image: bogerserge/nini-artgallery:latest
+ container_name: nini-artgallery
+ restart: unless-stopped
+ read_only: true
+ ports:
+ - "3000:3000"
+ volumes:
+ # User-writable data directory
+ - ./data:/app/data
+ # Temporary directories for Next.js cache
+ - /tmp
+ - /app/.next/cache
+ environment:
+ - NODE_ENV=production
+ # Required for SEO metadata, Open Graph, and absolute URLs in emails
+ - NEXT_PUBLIC_BASE_URL=https://your-domain.com
+ # Admin SMTP Configuration - Configure these in production
+ - ADMIN_EMAIL=admin@example.com
+ - SMTP_HOST=smtp.example.com
+ - SMTP_PORT=587
+ - SMTP_USER=smtp-user@example.com
+ - SMTP_PASS=your-smtp-password
+ - SMTP_FROM_NAME="Your Gallery Name"
+ - SMTP_SECURE=false
+ - SMTP_REQUIRE_TLS=true
+ - JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
+ # OpenRouter AI Chat - Get your key at https://openrouter.ai/keys
+ - OPENROUTER_API_KEY=your-openrouter-api-key
+ healthcheck:
+ test:
+ [
+ "CMD",
+ "wget",
+ "--no-verbose",
+ "--tries=1",
+ "--spider",
+ "http://127.0.0.1:3000/api/health",
+ ]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 20s
diff --git a/eslint.config.mjs b/eslint.config.mjs
index c85fb67..e2b76e6 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,16 +1,83 @@
-import { dirname } from "path";
-import { fileURLToPath } from "url";
-import { FlatCompat } from "@eslint/eslintrc";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const compat = new FlatCompat({
- baseDirectory: __dirname,
-});
+import js from "@eslint/js";
+import globals from "globals";
+import tseslint from "@typescript-eslint/eslint-plugin";
+import tsparser from "@typescript-eslint/parser";
const eslintConfig = [
- ...compat.extends("next/core-web-vitals", "next/typescript"),
+ {
+ ignores: [
+ "node_modules/**",
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ],
+ },
+ js.configs.recommended,
+ {
+ files: ["**/*.{js,mjs,cjs}"],
+ languageOptions: {
+ ecmaVersion: "latest",
+ sourceType: "module",
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ ...globals.es2021,
+ },
+ },
+ rules: {
+ "no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^_",
+ varsIgnorePattern: "^_",
+ caughtErrorsIgnorePattern: "^_",
+ ignoreRestSiblings: true,
+ },
+ ],
+ "no-console": "warn",
+ "prefer-const": "error",
+ "no-var": "error",
+ },
+ },
+ {
+ files: ["**/*.{ts,tsx}"],
+ languageOptions: {
+ parser: tsparser,
+ ecmaVersion: "latest",
+ sourceType: "module",
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ ...globals.es2021,
+ React: "readonly",
+ JSX: "readonly",
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ plugins: {
+ "@typescript-eslint": tseslint,
+ },
+ rules: {
+ "no-unused-vars": "off",
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^_",
+ varsIgnorePattern: "^_",
+ caughtErrorsIgnorePattern: "^_",
+ ignoreRestSiblings: true,
+ },
+ ],
+ "@typescript-eslint/no-unused-expressions": "error",
+ "@typescript-eslint/no-explicit-any": "warn",
+ "no-console": "warn",
+ },
+ },
];
export default eslintConfig;
diff --git a/messages/artist/alice/de.json b/messages/artist/alice/de.json
new file mode 100644
index 0000000..683b0d7
--- /dev/null
+++ b/messages/artist/alice/de.json
@@ -0,0 +1,4 @@
+{
+ "name": "Alice",
+ "description": "Eine digitale Künstlerin und Illustratorin, die fantastische Welten zum Leben erweckt."
+}
diff --git a/messages/artist/alice/en.json b/messages/artist/alice/en.json
new file mode 100644
index 0000000..3627e20
--- /dev/null
+++ b/messages/artist/alice/en.json
@@ -0,0 +1,4 @@
+{
+ "name": "Alice",
+ "description": "A digital artist and illustrator who brings fantastical worlds to life."
+}
diff --git a/messages/artist/alice/es.json b/messages/artist/alice/es.json
new file mode 100644
index 0000000..2d4a22e
--- /dev/null
+++ b/messages/artist/alice/es.json
@@ -0,0 +1,4 @@
+{
+ "name": "Alice",
+ "description": "Una artista digital e ilustradora que da vida a mundos fantásticos."
+}
diff --git a/messages/artist/alice/ka.json b/messages/artist/alice/ka.json
new file mode 100644
index 0000000..339bab6
--- /dev/null
+++ b/messages/artist/alice/ka.json
@@ -0,0 +1,4 @@
+{
+ "name": "ალისი",
+ "description": "ციფრული მხატვარი და ილუსტრატორი, რომელიც ფანტასტიკურ სამყაროებს აცოცხლებს."
+}
diff --git a/messages/artist/alice/ru.json b/messages/artist/alice/ru.json
new file mode 100644
index 0000000..5a7827f
--- /dev/null
+++ b/messages/artist/alice/ru.json
@@ -0,0 +1,4 @@
+{
+ "name": "Алиса",
+ "description": "Цифровой художник и иллюстратор, оживляющая фантастические миры."
+}
diff --git a/messages/artist/alice/tr.json b/messages/artist/alice/tr.json
new file mode 100644
index 0000000..c6ae9ac
--- /dev/null
+++ b/messages/artist/alice/tr.json
@@ -0,0 +1,4 @@
+{
+ "name": "Alice",
+ "description": "Fantastik dünyaları hayata geçiren dijital bir sanatçı ve illüstratör."
+}
diff --git a/messages/artist/nini/de.json b/messages/artist/nini/de.json
new file mode 100644
index 0000000..5f8d7d3
--- /dev/null
+++ b/messages/artist/nini/de.json
@@ -0,0 +1,4 @@
+{
+ "name": "Künstlername",
+ "description": "Künstlerbeschreibung und Biografie. Dieser Text wird auf der Galerie-Startseite angezeigt und kann über das Admin-Panel angepasst werden."
+}
diff --git a/messages/artist/nini/en.json b/messages/artist/nini/en.json
new file mode 100644
index 0000000..7265d38
--- /dev/null
+++ b/messages/artist/nini/en.json
@@ -0,0 +1,4 @@
+{
+ "name": "Artist Name",
+ "description": "Artist description and biography. This text will be displayed on the gallery homepage and can be customized through the admin panel."
+}
diff --git a/messages/artist/nini/es.json b/messages/artist/nini/es.json
new file mode 100644
index 0000000..36ebea3
--- /dev/null
+++ b/messages/artist/nini/es.json
@@ -0,0 +1,4 @@
+{
+ "name": "Nombre del Artista",
+ "description": "Descripción y biografía del artista. Este texto se mostrará en la página de inicio de la galería y puede personalizarse a través del panel de administración."
+}
diff --git a/messages/artist/nini/ka.json b/messages/artist/nini/ka.json
new file mode 100644
index 0000000..b115199
--- /dev/null
+++ b/messages/artist/nini/ka.json
@@ -0,0 +1,4 @@
+{
+ "name": "მხატვრის სახელი",
+ "description": "მხატვრის აღწერა და ბიოგრაფია. ეს ტექსტი გამოჩნდება გალერეის მთავარ გვერდზე და შეიძლება მორგებული იყოს ადმინისტრაციული პანელის მეშვეობით."
+}
diff --git a/messages/artist/nini/ru.json b/messages/artist/nini/ru.json
new file mode 100644
index 0000000..a7f1144
--- /dev/null
+++ b/messages/artist/nini/ru.json
@@ -0,0 +1,4 @@
+{
+ "name": "Имя художника",
+ "description": "Описание и биография художника. Этот текст будет отображаться на главной странице галереи и может быть настроен через панель администратора."
+}
diff --git a/messages/artist/nini/tr.json b/messages/artist/nini/tr.json
new file mode 100644
index 0000000..21f07e6
--- /dev/null
+++ b/messages/artist/nini/tr.json
@@ -0,0 +1,4 @@
+{
+ "name": "Sanatçı Adı",
+ "description": "Sanatçı açıklaması ve biyografisi. Bu metin galeri ana sayfasında görüntülenecek ve yönetici paneli aracılığıyla özelleştirilebilir."
+}
diff --git a/messages/artworks/alice/de.json b/messages/artworks/alice/de.json
new file mode 100644
index 0000000..5bf2334
--- /dev/null
+++ b/messages/artworks/alice/de.json
@@ -0,0 +1,39 @@
+{
+ "Categories": {
+ "paintings": {
+ "title": "Gemälde & Digitale Kunst",
+ "description": "Traumhafte Gemälde, die Fantasie und Emotion verbinden"
+ },
+ "origami": {
+ "title": "Illustrationen",
+ "description": "Handgezeichnete Tuscheillustrationen, inspiriert von Märchen"
+ }
+ },
+ "Artworks": {
+ "p1": {
+ "title": "Der Grinsekatzen-Wald",
+ "description": "Ein leuchtender Wald, in dem die Bäume zurückgrinsen — inspiriert vom Wunderland"
+ },
+ "p2": {
+ "title": "Mondscheinteich",
+ "description": "Aquarellstudie von Mondlicht auf stillem Wasser mit Glühwürmchen"
+ },
+ "p3": {
+ "title": "Der Garten der Roten Königin",
+ "description": "Ein surrealer Garten, in dem jede Rose ihr eigenes Temperament hat"
+ },
+ "o1": {
+ "title": "Das Weiße Kaninchen",
+ "description": "Aquarellporträt eines panischen Kaninchens mit Taschenuhr"
+ },
+ "o2": {
+ "title": "Pilzskizzen",
+ "description": "Tuschestudien fantastischer Pilze — manche größer als Häuser"
+ }
+ },
+ "Mediums": {
+ "digitalPainting": "Digitale Malerei",
+ "watercolorOnPaper": "Aquarell auf Papier",
+ "inkOnPaper": "Tusche auf Papier"
+ }
+}
diff --git a/messages/artworks/alice/en.json b/messages/artworks/alice/en.json
new file mode 100644
index 0000000..ede0132
--- /dev/null
+++ b/messages/artworks/alice/en.json
@@ -0,0 +1,39 @@
+{
+ "Categories": {
+ "paintings": {
+ "title": "Paintings & Digital Art",
+ "description": "Dreamlike paintings blending fantasy and emotion"
+ },
+ "origami": {
+ "title": "Illustrations",
+ "description": "Hand-drawn and ink illustrations inspired by fairy tales"
+ }
+ },
+ "Artworks": {
+ "p1": {
+ "title": "The Cheshire Forest",
+ "description": "A glowing forest where the trees grin back at you — inspired by Wonderland"
+ },
+ "p2": {
+ "title": "Moonlit Pond",
+ "description": "Watercolor study of moonlight reflecting on still water, with fireflies"
+ },
+ "p3": {
+ "title": "Red Queen's Garden",
+ "description": "A surreal garden where every rose has its own little temper"
+ },
+ "o1": {
+ "title": "The White Rabbit",
+ "description": "Watercolor portrait of a panicked rabbit clutching a pocket watch"
+ },
+ "o2": {
+ "title": "Mushroom Sketches",
+ "description": "Ink studies of fantastical mushrooms — some taller than houses"
+ }
+ },
+ "Mediums": {
+ "digitalPainting": "Digital painting",
+ "watercolorOnPaper": "Watercolor on paper",
+ "inkOnPaper": "Ink on paper"
+ }
+}
diff --git a/messages/artworks/alice/es.json b/messages/artworks/alice/es.json
new file mode 100644
index 0000000..e007f5d
--- /dev/null
+++ b/messages/artworks/alice/es.json
@@ -0,0 +1,39 @@
+{
+ "Categories": {
+ "paintings": {
+ "title": "Pinturas y Arte Digital",
+ "description": "Pinturas oníricas que mezclan fantasía y emoción"
+ },
+ "origami": {
+ "title": "Ilustraciones",
+ "description": "Ilustraciones a mano y tinta inspiradas en cuentos de hadas"
+ }
+ },
+ "Artworks": {
+ "p1": {
+ "title": "El Bosque del Gato de Cheshire",
+ "description": "Un bosque resplandeciente donde los árboles te devuelven la sonrisa — inspirado en el País de las Maravillas"
+ },
+ "p2": {
+ "title": "Estanque a la Luz de la Luna",
+ "description": "Estudio en acuarela de la luz de la luna reflejada en agua quieta, con luciérnagas"
+ },
+ "p3": {
+ "title": "El Jardín de la Reina Roja",
+ "description": "Un jardín surrealista donde cada rosa tiene su propio carácter"
+ },
+ "o1": {
+ "title": "El Conejo Blanco",
+ "description": "Retrato en acuarela de un conejo asustado agarrando un reloj de bolsillo"
+ },
+ "o2": {
+ "title": "Bocetos de Hongos",
+ "description": "Estudios a tinta de hongos fantásticos — algunos más altos que casas"
+ }
+ },
+ "Mediums": {
+ "digitalPainting": "Pintura digital",
+ "watercolorOnPaper": "Acuarela sobre papel",
+ "inkOnPaper": "Tinta sobre papel"
+ }
+}
diff --git a/messages/artworks/alice/ka.json b/messages/artworks/alice/ka.json
new file mode 100644
index 0000000..af1b6a7
--- /dev/null
+++ b/messages/artworks/alice/ka.json
@@ -0,0 +1,39 @@
+{
+ "Categories": {
+ "paintings": {
+ "title": "ნახატები და ციფრული ხელოვნება",
+ "description": "ოცნებისებური ნახატები, რომლებიც ფანტაზიასა და ემოციას აერთიანებს"
+ },
+ "origami": {
+ "title": "ილუსტრაციები",
+ "description": "ხელით დახატული და მელნის ილუსტრაციები, ზღაპრებით შთაგონებული"
+ }
+ },
+ "Artworks": {
+ "p1": {
+ "title": "ჩეშირის ტყე",
+ "description": "მბრწყინავი ტყე, სადაც ხეები ღიმილით გპასუხობენ — საოცრებათა ქვეყნით შთაგონებული"
+ },
+ "p2": {
+ "title": "მთვარის შუქზე აუზი",
+ "description": "აკვარელის ეტიუდი — მთვარის შუქი წყნარ წყალზე, ციცინათელებით"
+ },
+ "p3": {
+ "title": "წითელი დედოფლის ბაღი",
+ "description": "სიურეალისტური ბაღი, სადაც ყოველ ვარდს თავისი ხასიათი აქვს"
+ },
+ "o1": {
+ "title": "თეთრი კურდღელი",
+ "description": "აკვარელის პორტრეტი — შეშინებული კურდღელი ჯიბის საათით"
+ },
+ "o2": {
+ "title": "სოკოს ესკიზები",
+ "description": "მელნის ეტიუდები ფანტასტიკური სოკოების — ზოგი სახლებზე მაღალი"
+ }
+ },
+ "Mediums": {
+ "digitalPainting": "ციფრული ნახატი",
+ "watercolorOnPaper": "აკვარელი ქაღალდზე",
+ "inkOnPaper": "მელანი ქაღალდზე"
+ }
+}
diff --git a/messages/artworks/alice/ru.json b/messages/artworks/alice/ru.json
new file mode 100644
index 0000000..4b55aa7
--- /dev/null
+++ b/messages/artworks/alice/ru.json
@@ -0,0 +1,39 @@
+{
+ "Categories": {
+ "paintings": {
+ "title": "Картины и цифровое искусство",
+ "description": "Мечтательные картины, сочетающие фантазию и эмоции"
+ },
+ "origami": {
+ "title": "Иллюстрации",
+ "description": "Рисунки тушью и акварелью, вдохновлённые сказками"
+ }
+ },
+ "Artworks": {
+ "p1": {
+ "title": "Чеширский лес",
+ "description": "Светящийся лес, где деревья улыбаются в ответ — вдохновлено Страной чудес"
+ },
+ "p2": {
+ "title": "Лунный пруд",
+ "description": "Акварельный этюд лунного света на спокойной воде со светлячками"
+ },
+ "p3": {
+ "title": "Сад Красной Королевы",
+ "description": "Сюрреалистический сад, где у каждой розы свой норов"
+ },
+ "o1": {
+ "title": "Белый Кролик",
+ "description": "Акварельный портрет испуганного кролика с карманными часами"
+ },
+ "o2": {
+ "title": "Зарисовки грибов",
+ "description": "Тушевые этюды фантастических грибов — некоторые выше домов"
+ }
+ },
+ "Mediums": {
+ "digitalPainting": "Цифровая живопись",
+ "watercolorOnPaper": "Акварель на бумаге",
+ "inkOnPaper": "Тушь на бумаге"
+ }
+}
diff --git a/messages/artworks/alice/tr.json b/messages/artworks/alice/tr.json
new file mode 100644
index 0000000..444d044
--- /dev/null
+++ b/messages/artworks/alice/tr.json
@@ -0,0 +1,39 @@
+{
+ "Categories": {
+ "paintings": {
+ "title": "Tablolar ve Dijital Sanat",
+ "description": "Fantezi ve duyguyu harmanlayan rüya gibi tablolar"
+ },
+ "origami": {
+ "title": "İllüstrasyonlar",
+ "description": "Peri masallarından ilham alan el çizimi ve mürekkep illüstrasyonları"
+ }
+ },
+ "Artworks": {
+ "p1": {
+ "title": "Cheshire Ormanı",
+ "description": "Ağaçların size gülümsediği parlayan bir orman — Harikalar Diyarı'ndan ilham"
+ },
+ "p2": {
+ "title": "Ay Işığında Gölet",
+ "description": "Durgun suda ay ışığının yansımasını ateş böcekleriyle anlatan suluboya çalışması"
+ },
+ "p3": {
+ "title": "Kızıl Kraliçe'nin Bahçesi",
+ "description": "Her gülün kendine has bir huyu olan sürreal bir bahçe"
+ },
+ "o1": {
+ "title": "Beyaz Tavşan",
+ "description": "Cep saatine sımsıkı sarılmış panik halindeki bir tavşanın suluboya portresi"
+ },
+ "o2": {
+ "title": "Mantar Eskizleri",
+ "description": "Fantastik mantarların mürekkep çalışmaları — bazıları evlerden daha uzun"
+ }
+ },
+ "Mediums": {
+ "digitalPainting": "Dijital resim",
+ "watercolorOnPaper": "Kağıt üzerine suluboya",
+ "inkOnPaper": "Kağıt üzerine mürekkep"
+ }
+}
diff --git a/messages/artworks/nini/de.json b/messages/artworks/nini/de.json
new file mode 100644
index 0000000..042101c
--- /dev/null
+++ b/messages/artworks/nini/de.json
@@ -0,0 +1,103 @@
+{
+ "Categories": {
+ "origami": {
+ "title": "Origami-Kreationen",
+ "description": "Kunstvolle Papierfaltkunst, die Präzision und Kreativität zeigt"
+ },
+ "crochet": {
+ "title": "Häkelarbeiten",
+ "description": "Handgefertigte Häkelstücke, die traditionelle Techniken mit modernem Design verbinden"
+ },
+ "paintings": {
+ "title": "Gemälde",
+ "description": "Originale Gemälde, die verschiedene Stile und Techniken erkunden"
+ },
+ "fingernails": {
+ "title": "Nagelkunst",
+ "description": "Kreative und detaillierte Nagelkunst-Designs"
+ }
+ },
+ "Artworks": {
+ "o1": {
+ "title": "Papierkranich-Serie",
+ "description": "Traditionelle japanische Kranich-Designs in lebendigen Farben"
+ },
+ "o2": {
+ "title": "Geometrische Tiere",
+ "description": "Moderne Interpretation von Wildtieren durch geometrische Faltungen"
+ },
+ "o3": {
+ "title": "Springfrosch",
+ "description": "Traditioneller Origami-Frosch, der tatsächlich hüpfen kann! Ein spielerisches Stück, das Form und Funktion verbindet"
+ },
+ "o4": {
+ "title": "Modulare Würfelbox",
+ "description": "Komplexes modulares Origami, das einen dekorativen Würfel bildet, der sich öffnen und schließen lässt"
+ },
+ "o5": {
+ "title": "Liebesherz",
+ "description": "Elegantes herzförmiges Origami-Design mit zarten Falten und Kurven"
+ },
+ "c1": {
+ "title": "Kleine Tierfamilie",
+ "description": "Sammlung kleiner, süßer Stofftiere aus weicher Wolle"
+ },
+ "c2": {
+ "title": "Moderne Decke",
+ "description": "Zeitgenössische geometrische Musterdecke in Pastelltönen"
+ },
+ "c3": {
+ "title": "Freundlicher Frosch",
+ "description": "Bezaubernder Häkelfrosch mit großen Augen und einem fröhlichen Lächeln"
+ },
+ "c4": {
+ "title": "Flauschiger Hase",
+ "description": "Süßer Häkelhase mit Schlappohren und einem winzigen Baumwollschwanz"
+ },
+ "c5": {
+ "title": "Himmelskind",
+ "description": "Bezauberndes Häkel-Himmelskind aus Sky: Children of the Light, komplett mit Umhang und leuchtendem Herzen"
+ },
+ "c6": {
+ "title": "Gemütlicher Biber",
+ "description": "Ein absolut verrückter Biber mit wilden Kulleraugen, riesigen Nagezähnen und einem Schwanz um aufs Wasser zu knallen! Dieser kleine Wahnsinnige ist bereit, den chaotischsten Damm zu bauen, den Sie je gesehen haben!"
+ },
+ "p1": {
+ "title": "Abstrakter Sonnenuntergang",
+ "description": "Lebendige Interpretation eines Küstensonnenuntergangs"
+ },
+ "p2": {
+ "title": "Stadtleben",
+ "description": "Stadtszenen im impressionistischen Stil eingefangen"
+ },
+ "p3": {
+ "title": "Alice im Wunderland",
+ "description": "Fantasievolle Szene aus Alice im Wunderland mit der Teeparty des verrückten Hutmachers"
+ },
+ "n1": {
+ "title": "Frühlingskollektion",
+ "description": "Blumenmuster mit metallischen Akzenten"
+ },
+ "n2": {
+ "title": "Abstrakte Muster",
+ "description": "Geometrische und abstrakte Designs in kräftigen Farben"
+ },
+ "n3": {
+ "title": "Herzen & Liebe",
+ "description": "Romantische Herzmuster mit Glitzer und zarten Details"
+ }
+ },
+ "Mediums": {
+ "washiPaper": "Washi-Papier",
+ "origamiPaper": "Origami-Papier",
+ "specialtyOrigamiPaper": "Spezial-Origami-Papier",
+ "merinoWool": "Merinowolle",
+ "cottonYarn": "Baumwollgarn",
+ "softAcrylicYarn": "Weiches Acrylgarn",
+ "acrylicOnCanvas": "Acryl auf Leinwand",
+ "oilOnCanvas": "Öl auf Leinwand",
+ "mixedMediaOnCanvas": "Mischtechnik auf Leinwand",
+ "gelPolish": "Gel-Nagellack",
+ "regularPolish": "Normaler Nagellack"
+ }
+}
diff --git a/messages/artworks/nini/en.json b/messages/artworks/nini/en.json
new file mode 100644
index 0000000..b54e030
--- /dev/null
+++ b/messages/artworks/nini/en.json
@@ -0,0 +1,103 @@
+{
+ "Categories": {
+ "origami": {
+ "title": "Origami Creations",
+ "description": "Intricate paper folding art pieces showcasing precision and creativity"
+ },
+ "crochet": {
+ "title": "Crochet Items",
+ "description": "Handcrafted crochet pieces combining traditional techniques with modern design"
+ },
+ "paintings": {
+ "title": "Paintings",
+ "description": "Original paintings exploring various styles and techniques"
+ },
+ "fingernails": {
+ "title": "Fingernail Art",
+ "description": "Creative and detailed nail art designs"
+ }
+ },
+ "Artworks": {
+ "o1": {
+ "title": "Paper Crane Series",
+ "description": "Traditional Japanese crane designs in vibrant colors"
+ },
+ "o2": {
+ "title": "Geometric Animals",
+ "description": "Modern interpretation of wildlife through geometric folds"
+ },
+ "o3": {
+ "title": "Jumping Frog",
+ "description": "Traditional origami frog that can actually hop! A playful piece combining form and function"
+ },
+ "o4": {
+ "title": "Modular Cube Box",
+ "description": "Complex modular origami forming a decorative cube that can open and close"
+ },
+ "o5": {
+ "title": "Love Heart",
+ "description": "Elegant heart-shaped origami design with delicate folds and curves"
+ },
+ "c1": {
+ "title": "Tiny Animal Family",
+ "description": "Collection of small, cute stuffed animals made with soft wool"
+ },
+ "c2": {
+ "title": "Modern Blanket",
+ "description": "Contemporary geometric pattern blanket in pastel tones"
+ },
+ "c3": {
+ "title": "Friendly Frog",
+ "description": "Adorable crochet frog with big eyes and a cheerful smile"
+ },
+ "c4": {
+ "title": "Fluffy Bunny",
+ "description": "Sweet crochet rabbit with floppy ears and a tiny cotton tail"
+ },
+ "c5": {
+ "title": "Sky Kid",
+ "description": "Adorable crochet Sky Kid from Sky: Children of the Light, complete with cape and glowing heart"
+ },
+ "c6": {
+ "title": "Cozy Beaver",
+ "description": "An absolutely bonkers beaver with wild googly eyes, oversized buck teeth, and a tail for slapping the water! This little maniac is ready to build the most chaotic dam you've ever seen."
+ },
+ "p1": {
+ "title": "Abstract Sunset",
+ "description": "Vibrant interpretation of a coastal sunset"
+ },
+ "p2": {
+ "title": "Urban Life",
+ "description": "City scenes captured in impressionistic style"
+ },
+ "p3": {
+ "title": "Alice in Wonderland",
+ "description": "Whimsical scene from Alice in Wonderland featuring the Mad Hatter's tea party"
+ },
+ "n1": {
+ "title": "Spring Collection",
+ "description": "Floral patterns with metallic accents"
+ },
+ "n2": {
+ "title": "Abstract Patterns",
+ "description": "Geometric and abstract designs in bold colors"
+ },
+ "n3": {
+ "title": "Hearts & Love",
+ "description": "Romantic heart patterns with glitter and delicate details"
+ }
+ },
+ "Mediums": {
+ "washiPaper": "Washi paper",
+ "origamiPaper": "Origami paper",
+ "specialtyOrigamiPaper": "Specialty origami paper",
+ "merinoWool": "Merino wool",
+ "cottonYarn": "Cotton yarn",
+ "softAcrylicYarn": "Soft acrylic yarn",
+ "acrylicOnCanvas": "Acrylic on canvas",
+ "oilOnCanvas": "Oil on canvas",
+ "mixedMediaOnCanvas": "Mixed media on canvas",
+ "gelPolish": "Gel polish",
+ "regularPolish": "Regular polish"
+ }
+}
diff --git a/messages/artworks/nini/es.json b/messages/artworks/nini/es.json
new file mode 100644
index 0000000..fb42c56
--- /dev/null
+++ b/messages/artworks/nini/es.json
@@ -0,0 +1,103 @@
+{
+ "Categories": {
+ "origami": {
+ "title": "Creaciones de Origami",
+ "description": "Piezas de arte de plegado de papel intrincadas que muestran precisión y creatividad"
+ },
+ "crochet": {
+ "title": "Artículos de Ganchillo",
+ "description": "Piezas de ganchillo hechas a mano que combinan técnicas tradicionales con diseño moderno"
+ },
+ "paintings": {
+ "title": "Pinturas",
+ "description": "Pinturas originales que exploran varios estilos y técnicas"
+ },
+ "fingernails": {
+ "title": "Arte de Uñas",
+ "description": "Diseños creativos y detallados de arte de uñas"
+ }
+ },
+ "Artworks": {
+ "o1": {
+ "title": "Serie de Grullas de Papel",
+ "description": "Diseños tradicionales japoneses de grullas en colores vibrantes"
+ },
+ "o2": {
+ "title": "Animales Geométricos",
+ "description": "Interpretación moderna de la vida silvestre a través de pliegues geométricos"
+ },
+ "o3": {
+ "title": "Rana Saltarina",
+ "description": "¡Rana de origami tradicional que realmente puede saltar! Una pieza juguetona que combina forma y función"
+ },
+ "o4": {
+ "title": "Caja Cubo Modular",
+ "description": "Origami modular complejo que forma un cubo decorativo que puede abrirse y cerrarse"
+ },
+ "o5": {
+ "title": "Corazón de Amor",
+ "description": "Diseño elegante de origami en forma de corazón con pliegues y curvas delicadas"
+ },
+ "c1": {
+ "title": "Familia de Animales Pequeños",
+ "description": "Colección de pequeños y lindos animales de peluche hechos con lana suave"
+ },
+ "c2": {
+ "title": "Manta Moderna",
+ "description": "Manta de patrón geométrico contemporáneo en tonos pastel"
+ },
+ "c3": {
+ "title": "Rana Amigable",
+ "description": "Adorable rana de ganchillo con ojos grandes y una sonrisa alegre"
+ },
+ "c4": {
+ "title": "Conejito Esponjoso",
+ "description": "Dulce conejo de ganchillo con orejas caídas y una pequeña cola de algodón"
+ },
+ "c5": {
+ "title": "Niño del Cielo",
+ "description": "Adorable Niño del Cielo de ganchillo de Sky: Children of the Light, completo con capa y corazón brillante"
+ },
+ "c6": {
+ "title": "Castor Acogedor",
+ "description": "¡Un castor absolutamente loco con ojos saltones salvajes, dientes enormes y una cola para golpear el agua! ¡Este pequeño maníaco está listo para construir la presa más caótica que hayas visto jamás!"
+ },
+ "p1": {
+ "title": "Atardecer Abstracto",
+ "description": "Interpretación vibrante de un atardecer costero"
+ },
+ "p2": {
+ "title": "Vida Urbana",
+ "description": "Escenas de la ciudad capturadas en estilo impresionista"
+ },
+ "p3": {
+ "title": "Alicia en el País de las Maravillas",
+ "description": "Escena caprichosa de Alicia en el País de las Maravillas con la fiesta del té del Sombrerero Loco"
+ },
+ "n1": {
+ "title": "Colección de Primavera",
+ "description": "Patrones florales con acentos metálicos"
+ },
+ "n2": {
+ "title": "Patrones Abstractos",
+ "description": "Diseños geométricos y abstractos en colores audaces"
+ },
+ "n3": {
+ "title": "Corazones y Amor",
+ "description": "Patrones románticos de corazones con purpurina y detalles delicados"
+ }
+ },
+ "Mediums": {
+ "washiPaper": "Papel Washi",
+ "origamiPaper": "Papel de origami",
+ "specialtyOrigamiPaper": "Papel de origami especial",
+ "merinoWool": "Lana merino",
+ "cottonYarn": "Hilo de algodón",
+ "softAcrylicYarn": "Hilo acrílico suave",
+ "acrylicOnCanvas": "Acrílico sobre lienzo",
+ "oilOnCanvas": "Óleo sobre lienzo",
+ "mixedMediaOnCanvas": "Técnica mixta sobre lienzo",
+ "gelPolish": "Esmalte en gel",
+ "regularPolish": "Esmalte regular"
+ }
+}
diff --git a/messages/artworks/nini/ka.json b/messages/artworks/nini/ka.json
new file mode 100644
index 0000000..d105fe9
--- /dev/null
+++ b/messages/artworks/nini/ka.json
@@ -0,0 +1,103 @@
+{
+ "Categories": {
+ "origami": {
+ "title": "ორიგამის ნამუშევრები",
+ "description": "რთული ქაღალდის კეცვის ხელოვნების ნაწარმოებები, რომლებიც გამოირჩევა სიზუსტითა და შემოქმედებითობით"
+ },
+ "crochet": {
+ "title": "ქსოვის ნაწარმები",
+ "description": "ხელნაკეთი ქსოვის ნაწარმოები, რომლებიც აერთიანებს ტრადიციულ ტექნიკებს თანამედროვე დიზაინთან"
+ },
+ "paintings": {
+ "title": "ნახატები",
+ "description": "ორიგინალური ნახატები, რომლებიც იკვლევს სხვადასხვა სტილსა და ტექნიკას"
+ },
+ "fingernails": {
+ "title": "ფრჩხილების ხელოვნება",
+ "description": "შემოქმედებითი და დეტალური ფრჩხილების დიზაინები"
+ }
+ },
+ "Artworks": {
+ "o1": {
+ "title": "ქაღალდის ამწის სერია",
+ "description": "ტრადიციული იაპონური ამწის დიზაინები ცოცხალ ფერებში"
+ },
+ "o2": {
+ "title": "გეომეტრიული ცხოველები",
+ "description": "ველური ბუნების თანამედროვე ინტერპრეტაცია გეომეტრიული კეცვებით"
+ },
+ "o3": {
+ "title": "მხტუნავი ბაყაყი",
+ "description": "ტრადიციული ორიგამის ბაყაყი, რომელსაც ნამდვილად შეუძლია ხტუნვა! სახალისო ნაწარმოები, რომელიც აერთიანებს ფორმასა და ფუნქციას"
+ },
+ "o4": {
+ "title": "მოდულური კუბის ყუთი",
+ "description": "რთული მოდულური ორიგამი, რომელიც ქმნის დეკორატიულ კუბს, რომელსაც შეუძლია გაიხსნას და დაიხუროს"
+ },
+ "o5": {
+ "title": "სიყვარულის გული",
+ "description": "ელეგანტური გულის ფორმის ორიგამის დიზაინი ნაზი კეცვებითა და მოხვევებით"
+ },
+ "c1": {
+ "title": "პატარა ცხოველების ოჯახი",
+ "description": "პატარა, საყვარელი ბუმბულოვანი ცხოველების კოლექცია რბილი მატყლისგან"
+ },
+ "c2": {
+ "title": "თანამედროვე საბანი",
+ "description": "თანამედროვე გეომეტრიული ნიმუშის საბანი პასტელურ ტონებში"
+ },
+ "c3": {
+ "title": "მეგობრული ბაყაყი",
+ "description": "საყვარელი ქსოვის ბაყაყი დიდი თვალებითა და მხიარული ღიმილით"
+ },
+ "c4": {
+ "title": "ბუმბულოვანი კურდღელი",
+ "description": "ტკბილი ქსოვის კურდღელი ჩამოკიდებული ყურებითა და პატარა ბამბის კუდით"
+ },
+ "c5": {
+ "title": "ცის ბავშვი",
+ "description": "საყვარელი ქსოვის ცის ბავშვი Sky: Children of the Light-დან, სრული კაბითა და ნათელი გულით"
+ },
+ "c6": {
+ "title": "მყუდრო თახვი",
+ "description": "აბსოლუტურად გიჟი თახვი ველური გუგლის თვალებით, გადიდებული წინა კბილებითა და წყლის ცემისთვის განკუთვნილი კუდით! ეს პატარა მანიაკი მზადაა ააშენოს ყველაზე ქაოსური კაშხალი, რომელიც ოდესმე გინახავს."
+ },
+ "p1": {
+ "title": "აბსტრაქტული მზისჩასვლა",
+ "description": "სანაპირო მზისჩასვლის ცოცხალი ინტერპრეტაცია"
+ },
+ "p2": {
+ "title": "ურბანული ცხოვრება",
+ "description": "ქალაქის სცენები იმპრესიონისტული სტილით"
+ },
+ "p3": {
+ "title": "ალისა საოცრებათა ქვეყანაში",
+ "description": "ალისა საოცრებათა ქვეყანაში-ს ფანტასტიკური სცენა, რომელიც გამოსახავს გიჟი ქუდმაქრის ჩაის წვეულებას"
+ },
+ "n1": {
+ "title": "გაზაფხულის კოლექცია",
+ "description": "ყვავილოვანი ნიმუშები ლითონის აქცენტებით"
+ },
+ "n2": {
+ "title": "აბსტრაქტული ნიმუშები",
+ "description": "გეომეტრიული და აბსტრაქტული დიზაინები მკვეთრ ფერებში"
+ },
+ "n3": {
+ "title": "გულები და სიყვარული",
+ "description": "რომანტიული გულის ნიმუშები ბრჭყვიალითა და ნაზი დეტალებით"
+ }
+ },
+ "Mediums": {
+ "washiPaper": "ვაში ქაღალდი",
+ "origamiPaper": "ორიგამის ქაღალდი",
+ "specialtyOrigamiPaper": "სპეციალური ორიგამის ქაღალდი",
+ "merinoWool": "მერინოს მატყლი",
+ "cottonYarn": "ბამბის ძაფი",
+ "softAcrylicYarn": "რბილი აკრილის ძაფი",
+ "acrylicOnCanvas": "აკრილი ტილოზე",
+ "oilOnCanvas": "ზეთი ტილოზე",
+ "mixedMediaOnCanvas": "შერეული მასალა ტილოზე",
+ "gelPolish": "გელის ლაქი",
+ "regularPolish": "ჩვეულებრივი ლაქი"
+ }
+}
diff --git a/messages/artworks/nini/ru.json b/messages/artworks/nini/ru.json
new file mode 100644
index 0000000..aa6f0d1
--- /dev/null
+++ b/messages/artworks/nini/ru.json
@@ -0,0 +1,103 @@
+{
+ "Categories": {
+ "origami": {
+ "title": "Оригами",
+ "description": "Сложные произведения искусства складывания бумаги, демонстрирующие точность и творчество"
+ },
+ "crochet": {
+ "title": "Вязаные изделия",
+ "description": "Изделия ручной работы, сочетающие традиционные техники с современным дизайном"
+ },
+ "paintings": {
+ "title": "Картины",
+ "description": "Оригинальные картины, исследующие различные стили и техники"
+ },
+ "fingernails": {
+ "title": "Дизайн ногтей",
+ "description": "Креативные и детализированные дизайны для ногтей"
+ }
+ },
+ "Artworks": {
+ "o1": {
+ "title": "Серия бумажных журавликов",
+ "description": "Традиционные японские дизайны журавликов в ярких цветах"
+ },
+ "o2": {
+ "title": "Геометрические животные",
+ "description": "Современная интерпретация дикой природы через геометрические складки"
+ },
+ "o3": {
+ "title": "Прыгающая лягушка",
+ "description": "Традиционная лягушка оригами, которая действительно может прыгать! Игривое произведение, сочетающее форму и функцию"
+ },
+ "o4": {
+ "title": "Модульная кубическая коробка",
+ "description": "Сложное модульное оригами, образующее декоративный куб, который может открываться и закрываться"
+ },
+ "o5": {
+ "title": "Сердце любви",
+ "description": "Элегантный дизайн оригами в форме сердца с деликатными складками и изгибами"
+ },
+ "c1": {
+ "title": "Семья маленьких животных",
+ "description": "Коллекция маленьких, милых мягких игрушек, сделанных из мягкой шерсти"
+ },
+ "c2": {
+ "title": "Современное одеяло",
+ "description": "Современное одеяло с геометрическим узором в пастельных тонах"
+ },
+ "c3": {
+ "title": "Дружелюбная лягушка",
+ "description": "Очаровательная вязаная лягушка с большими глазами и веселой улыбкой"
+ },
+ "c4": {
+ "title": "Пушистый кролик",
+ "description": "Милый вязаный кролик с висячими ушами и крошечным хлопковым хвостиком"
+ },
+ "c5": {
+ "title": "Небесный ребенок",
+ "description": "Очаровательный вязаный Небесный ребенок из игры Sky: Children of the Light, с плащом и светящимся сердцем"
+ },
+ "c6": {
+ "title": "Уютный бобр",
+ "description": "Абсолютно безумный бобр с дикими выпученными глазами, огромными передними зубами и хвостом для шлепания по воде! Этот маленький маньяк готов построить самую хаотичную плотину, которую вы когда-либо видели."
+ },
+ "p1": {
+ "title": "Абстрактный закат",
+ "description": "Яркая интерпретация прибрежного заката"
+ },
+ "p2": {
+ "title": "Городская жизнь",
+ "description": "Городские сцены, запечатленные в импрессионистском стиле"
+ },
+ "p3": {
+ "title": "Алиса в стране чудес",
+ "description": "Причудливая сцена из Алисы в стране чудес с чаепитием Безумного Шляпника"
+ },
+ "n1": {
+ "title": "Весенняя коллекция",
+ "description": "Цветочные узоры с металлическими акцентами"
+ },
+ "n2": {
+ "title": "Абстрактные узоры",
+ "description": "Геометрические и абстрактные дизайны в ярких цветах"
+ },
+ "n3": {
+ "title": "Сердца и любовь",
+ "description": "Романтические узоры с сердцами, блестками и деликатными деталями"
+ }
+ },
+ "Mediums": {
+ "washiPaper": "Бумага васи",
+ "origamiPaper": "Бумага для оригами",
+ "specialtyOrigamiPaper": "Специальная бумага для оригами",
+ "merinoWool": "Шерсть мериноса",
+ "cottonYarn": "Хлопковая пряжа",
+ "softAcrylicYarn": "Мягкая акриловая пряжа",
+ "acrylicOnCanvas": "Акрил на холсте",
+ "oilOnCanvas": "Масло на холсте",
+ "mixedMediaOnCanvas": "Смешанная техника на холсте",
+ "gelPolish": "Гель-лак",
+ "regularPolish": "Обычный лак"
+ }
+}
diff --git a/messages/artworks/nini/tr.json b/messages/artworks/nini/tr.json
new file mode 100644
index 0000000..2cb4f31
--- /dev/null
+++ b/messages/artworks/nini/tr.json
@@ -0,0 +1,103 @@
+{
+ "Categories": {
+ "origami": {
+ "title": "Origami Kreasyonları",
+ "description": "Hassasiyet ve yaratıcılığı sergileyen karmaşık kağıt katlama sanat eserleri"
+ },
+ "crochet": {
+ "title": "Örgü Ürünleri",
+ "description": "Geleneksel teknikleri modern tasarımla birleştiren el yapımı örgü parçaları"
+ },
+ "paintings": {
+ "title": "Resimler",
+ "description": "Çeşitli stil ve teknikleri keşfeden orijinal resimler"
+ },
+ "fingernails": {
+ "title": "Tırnak Sanatı",
+ "description": "Yaratıcı ve detaylı tırnak sanatı tasarımları"
+ }
+ },
+ "Artworks": {
+ "o1": {
+ "title": "Kağıt Turna Serisi",
+ "description": "Canlı renklerde geleneksel Japon turna tasarımları"
+ },
+ "o2": {
+ "title": "Geometrik Hayvanlar",
+ "description": "Vahşi yaşamın geometrik katlamalarla modern yorumu"
+ },
+ "o3": {
+ "title": "Zıplayan Kurbağa",
+ "description": "Gerçekten zıplayabilen geleneksel origami kurbağa! Form ve işlevi birleştiren eğlenceli bir parça"
+ },
+ "o4": {
+ "title": "Modüler Küp Kutu",
+ "description": "Açılıp kapanabilen dekoratif bir küp oluşturan karmaşık modüler origami"
+ },
+ "o5": {
+ "title": "Aşk Kalbi",
+ "description": "Zarif katlamalar ve kıvrımlarla kalp şeklinde şık origami tasarımı"
+ },
+ "c1": {
+ "title": "Minik Hayvan Ailesi",
+ "description": "Yumuşak yünden yapılmış küçük, sevimli peluş hayvanlar koleksiyonu"
+ },
+ "c2": {
+ "title": "Modern Battaniye",
+ "description": "Pastel tonlarda çağdaş geometrik desenli battaniye"
+ },
+ "c3": {
+ "title": "Dost Kurbağa",
+ "description": "Büyük gözleri ve neşeli gülümsemesiyle sevimli örgü kurbağa"
+ },
+ "c4": {
+ "title": "Tüylü Tavşan",
+ "description": "Sarkık kulakları ve minik pamuk kuyruğuyla tatlı örgü tavşan"
+ },
+ "c5": {
+ "title": "Gökyüzü Çocuğu",
+ "description": "Sky: Children of the Light'tan sevimli örgü Gökyüzü Çocuğu, pelerini ve parlayan kalbiyle birlikte"
+ },
+ "c6": {
+ "title": "Rahat Kunduz",
+ "description": "Vahşi google gözleri, büyük ön dişleri ve suyu çarpmak için kuyruğuyla kesinlikle çılgın bir kunduz! Bu küçük manyak, şimdiye kadar gördüğünüz en kaotik barajı inşa etmeye hazır."
+ },
+ "p1": {
+ "title": "Soyut Gün Batımı",
+ "description": "Sahil gün batımının canlı yorumu"
+ },
+ "p2": {
+ "title": "Şehir Hayatı",
+ "description": "İzlenimci tarzda yakalanmış şehir manzaraları"
+ },
+ "p3": {
+ "title": "Harikalar Diyarında Alice",
+ "description": "Çılgın Şapkacı'nın çay partisini içeren Alice Harikalar Diyarında'ndan fantastik sahne"
+ },
+ "n1": {
+ "title": "İlkbahar Koleksiyonu",
+ "description": "Metalik vurgularla çiçek desenleri"
+ },
+ "n2": {
+ "title": "Soyut Desenler",
+ "description": "Cesur renklerde geometrik ve soyut tasarımlar"
+ },
+ "n3": {
+ "title": "Kalpler ve Aşk",
+ "description": "Sim ve zarif detaylarla romantik kalp desenleri"
+ }
+ },
+ "Mediums": {
+ "washiPaper": "Washi kağıdı",
+ "origamiPaper": "Origami kağıdı",
+ "specialtyOrigamiPaper": "Özel origami kağıdı",
+ "merinoWool": "Merino yünü",
+ "cottonYarn": "Pamuk ipliği",
+ "softAcrylicYarn": "Yumuşak akrilik iplik",
+ "acrylicOnCanvas": "Tuval üzerine akrilik",
+ "oilOnCanvas": "Tuval üzerine yağlıboya",
+ "mixedMediaOnCanvas": "Tuval üzerine karışık teknik",
+ "gelPolish": "Jel oje",
+ "regularPolish": "Normal oje"
+ }
+}
diff --git a/messages/ui/admin/de.json b/messages/ui/admin/de.json
new file mode 100644
index 0000000..e8e2cb2
--- /dev/null
+++ b/messages/ui/admin/de.json
@@ -0,0 +1,58 @@
+{
+ "Dashboard": {
+ "title": "Dashboard",
+ "subtitle": "Verwalten Sie Ihre Kunstgalerie-Einstellungen",
+ "checkingConfiguration": "Konfiguration wird überprüft…"
+ },
+ "Login": {
+ "title": "Anmeldung",
+ "emailStep": "Geben Sie Ihre E-Mail-Adresse ein, um einen Bestätigungscode zu erhalten",
+ "otpStep": "Geben Sie den Bestätigungscode ein, der an Ihre E-Mail gesendet wurde",
+ "notAvailable": "Admin-Anmeldung ist nicht verfügbar",
+ "configurationRequired": "Konfiguration erforderlich",
+ "configurationMessage": "Die Admin-Anmeldung erfordert SMTP- und Authentifizierungskonfiguration. Bitte überprüfen Sie die Server-Logs auf fehlende Umgebungsvariablen.",
+ "emailAddress": "E-Mail-Adresse",
+ "verificationCode": "Bestätigungscode",
+ "sendVerificationCode": "Bestätigungscode senden",
+ "verifyAndLogin": "Bestätigen & Anmelden",
+ "sentTo": "Gesendet an:",
+ "useDifferentEmail": "Andere E-Mail verwenden",
+ "loginSuccessful": "Anmeldung erfolgreich! Weiterleitung…",
+ "networkError": "Netzwerkfehler. Bitte versuchen Sie es erneut."
+ },
+ "Settings": {
+ "broadcastMessageSettings": "Broadcast-Nachrichten-Einstellungen",
+ "broadcastMessageDescription": "Konfigurieren Sie die Nachricht, die in Ihrer Galerie angezeigt wird",
+ "displayBroadcastMessage": "Broadcast-Nachricht in der Galerie anzeigen",
+ "title": "Titel",
+ "messageContent": "Nachrichteninhalt",
+ "allowDismiss": "Besuchern erlauben, die Nachricht zu schließen",
+ "saveChanges": "Änderungen speichern",
+ "settingsSaved": "Einstellungen erfolgreich gespeichert!",
+ "enterMessageTitle": "Nachrichtentitel eingeben",
+ "enterMessageContent": "Ihren Nachrichteninhalt eingeben",
+ "charactersCount": "{count}/{max} Zeichen"
+ },
+ "ArtistSettings": {
+ "title": "Künstlerprofil-Einstellungen",
+ "description": "Verwalten Sie Ihre Künstlerprofil-Informationen",
+ "artistName": "Künstlername",
+ "artistDescription": "Künstlerbeschreibung",
+ "enterArtistName": "Künstlername eingeben",
+ "enterArtistDescription": "Künstlerbeschreibung eingeben"
+ },
+ "Errors": {
+ "failedToLoadSettings": "Einstellungen konnten nicht geladen werden",
+ "failedToSaveSettings": "Einstellungen konnten nicht gespeichert werden",
+ "checkAdminConfiguration": "Admin-Konfiguration konnte nicht überprüft werden"
+ },
+ "Email": {
+ "subject": "Admin-Anmeldung - Ihr Bestätigungscode",
+ "title": "Admin-Anmeldung Bestätigung",
+ "codeMessage": "Ihr Bestätigungscode für den Admin-Zugang ist:",
+ "expiryMessage": "Dieser Code läuft in 10 Minuten ab.",
+ "ignoreMessage": "Falls Sie diesen Code nicht angefordert haben, ignorieren Sie diese E-Mail bitte.",
+ "automatedMessage": "Dies ist eine automatische Nachricht von {siteName}",
+ "textVersion": "Ihr Admin-Bestätigungscode ist: {otp}. Dieser Code läuft in 10 Minuten ab."
+ }
+}
diff --git a/messages/ui/admin/en.json b/messages/ui/admin/en.json
new file mode 100644
index 0000000..a31dcdc
--- /dev/null
+++ b/messages/ui/admin/en.json
@@ -0,0 +1,58 @@
+{
+ "Dashboard": {
+ "title": "Dashboard",
+ "subtitle": "Manage your art gallery settings",
+ "checkingConfiguration": "Checking configuration…"
+ },
+ "Login": {
+ "title": "Login",
+ "emailStep": "Enter your email to receive a verification code",
+ "otpStep": "Enter the verification code sent to your email",
+ "notAvailable": "Admin login is not available",
+ "configurationRequired": "Configuration Required",
+ "configurationMessage": "Admin login requires SMTP and authentication configuration. Please check the server logs for missing environment variables.",
+ "emailAddress": "Email address",
+ "verificationCode": "Verification Code",
+ "sendVerificationCode": "Send Verification Code",
+ "verifyAndLogin": "Verify & Login",
+ "sentTo": "Sent to:",
+ "useDifferentEmail": "Use different email",
+ "loginSuccessful": "Login successful! Redirecting…",
+ "networkError": "Network error. Please try again."
+ },
+ "Settings": {
+ "broadcastMessageSettings": "Broadcast Message Settings",
+ "broadcastMessageDescription": "Configure the message displayed on your gallery",
+ "displayBroadcastMessage": "Display broadcast message on gallery",
+ "title": "Title",
+ "messageContent": "Message Content",
+ "allowDismiss": "Allow visitors to dismiss the message",
+ "saveChanges": "Save Changes",
+ "settingsSaved": "Settings saved successfully!",
+ "enterMessageTitle": "Enter message title",
+ "enterMessageContent": "Enter your message content",
+ "charactersCount": "{count}/{max} characters"
+ },
+ "ArtistSettings": {
+ "title": "Artist Profile Settings",
+ "description": "Manage your artist profile information",
+ "artistName": "Artist Name",
+ "artistDescription": "Artist Description",
+ "enterArtistName": "Enter artist name",
+ "enterArtistDescription": "Enter artist description"
+ },
+ "Errors": {
+ "failedToLoadSettings": "Failed to load settings",
+ "failedToSaveSettings": "Failed to save settings",
+ "checkAdminConfiguration": "Unable to check admin configuration"
+ },
+ "Email": {
+ "subject": "Admin Login - Your Verification Code",
+ "title": "Admin Login Verification",
+ "codeMessage": "Your verification code for admin access is:",
+ "expiryMessage": "This code will expire in 10 minutes.",
+ "ignoreMessage": "If you didn't request this code, please ignore this email.",
+ "automatedMessage": "This is an automated message from {siteName}",
+ "textVersion": "Your admin verification code is: {otp}. This code will expire in 10 minutes."
+ }
+}
diff --git a/messages/ui/admin/es.json b/messages/ui/admin/es.json
new file mode 100644
index 0000000..3fa84dd
--- /dev/null
+++ b/messages/ui/admin/es.json
@@ -0,0 +1,58 @@
+{
+ "Dashboard": {
+ "title": "Panel de Control",
+ "subtitle": "Gestiona la configuración de tu galería de arte",
+ "checkingConfiguration": "Verificando configuración…"
+ },
+ "Login": {
+ "title": "Inicio de Sesión",
+ "emailStep": "Ingresa tu email para recibir un código de verificación",
+ "otpStep": "Ingresa el código de verificación enviado a tu email",
+ "notAvailable": "El inicio de sesión de administrador no está disponible",
+ "configurationRequired": "Configuración Requerida",
+ "configurationMessage": "El inicio de sesión de administrador requiere configuración SMTP y de autenticación. Por favor revisa los logs del servidor para variables de entorno faltantes.",
+ "emailAddress": "Dirección de email",
+ "verificationCode": "Código de Verificación",
+ "sendVerificationCode": "Enviar Código de Verificación",
+ "verifyAndLogin": "Verificar e Iniciar Sesión",
+ "sentTo": "Enviado a:",
+ "useDifferentEmail": "Usar email diferente",
+ "loginSuccessful": "¡Inicio de sesión exitoso! Redirigiendo…",
+ "networkError": "Error de red. Por favor intenta de nuevo."
+ },
+ "Settings": {
+ "broadcastMessageSettings": "Configuración de Mensaje de Difusión",
+ "broadcastMessageDescription": "Configura el mensaje mostrado en tu galería",
+ "displayBroadcastMessage": "Mostrar mensaje de difusión en la galería",
+ "title": "Título",
+ "messageContent": "Contenido del Mensaje",
+ "allowDismiss": "Permitir a los visitantes cerrar el mensaje",
+ "saveChanges": "Guardar Cambios",
+ "settingsSaved": "¡Configuración guardada exitosamente!",
+ "enterMessageTitle": "Ingresa el título del mensaje",
+ "enterMessageContent": "Ingresa el contenido de tu mensaje",
+ "charactersCount": "{count}/{max} caracteres"
+ },
+ "ArtistSettings": {
+ "title": "Configuración del Perfil del Artista",
+ "description": "Gestiona la información de tu perfil de artista",
+ "artistName": "Nombre del Artista",
+ "artistDescription": "Descripción del Artista",
+ "enterArtistName": "Ingresa el nombre del artista",
+ "enterArtistDescription": "Ingresa la descripción del artista"
+ },
+ "Errors": {
+ "failedToLoadSettings": "Error al cargar la configuración",
+ "failedToSaveSettings": "Error al guardar la configuración",
+ "checkAdminConfiguration": "No se pudo verificar la configuración de administrador"
+ },
+ "Email": {
+ "subject": "Inicio de Sesión de Administrador - Su Código de Verificación",
+ "title": "Verificación de Inicio de Sesión de Administrador",
+ "codeMessage": "Su código de verificación para acceso de administrador es:",
+ "expiryMessage": "Este código expirará en 10 minutos.",
+ "ignoreMessage": "Si no solicitó este código, por favor ignore este email.",
+ "automatedMessage": "Este es un mensaje automatizado de {siteName}",
+ "textVersion": "Su código de verificación de administrador es: {otp}. Este código expirará en 10 minutos."
+ }
+}
diff --git a/messages/ui/admin/ka.json b/messages/ui/admin/ka.json
new file mode 100644
index 0000000..4a3fb76
--- /dev/null
+++ b/messages/ui/admin/ka.json
@@ -0,0 +1,58 @@
+{
+ "Dashboard": {
+ "title": "მართვის პანელი",
+ "subtitle": "მართეთ თქვენი ხელოვნების გალერეის პარამეტრები",
+ "checkingConfiguration": "კონფიგურაციის შემოწმება…"
+ },
+ "Login": {
+ "title": "შესვლა",
+ "emailStep": "შეიყვანეთ თქვენი ელ-ფოსტა დადასტურების კოდის მისაღებად",
+ "otpStep": "შეიყვანეთ დადასტურების კოდი, რომელიც გამოიგზავნა თქვენს ელ-ფოსტაზე",
+ "notAvailable": "ადმინისტრატორის შესვლა მიუწვდომელია",
+ "configurationRequired": "საჭიროა კონფიგურაცია",
+ "configurationMessage": "ადმინისტრატორის შესვლა საჭიროებს SMTP და ავთენტიფიკაციის კონფიგურაციას. გთხოვთ შეამოწმოთ სერვერის ლოგები გამოტოვებული გარემოს ცვლადებისთვის.",
+ "emailAddress": "ელ-ფოსტის მისამართი",
+ "verificationCode": "დადასტურების კოდი",
+ "sendVerificationCode": "დადასტურების კოდის გაგზავნა",
+ "verifyAndLogin": "დადასტურება და შესვლა",
+ "sentTo": "გაიგზავნა:",
+ "useDifferentEmail": "სხვა ელ-ფოსტის გამოყენება",
+ "loginSuccessful": "შესვლა წარმატებულია! გადამისამართება…",
+ "networkError": "ქსელის შეცდომა. გთხოვთ სცადოთ ხელახლა."
+ },
+ "Settings": {
+ "broadcastMessageSettings": "ტრანსლაციის შეტყობინების პარამეტრები",
+ "broadcastMessageDescription": "კონფიგურაცია შეტყობინებისა, რომელიც ნაჩვენებია თქვენს გალერეაში",
+ "displayBroadcastMessage": "ტრანსლაციის შეტყობინების ჩვენება გალერეაში",
+ "title": "სათაური",
+ "messageContent": "შეტყობინების შინაარსი",
+ "allowDismiss": "ვიზიტორებს შეუძლიათ შეტყობინების დახურვა",
+ "saveChanges": "ცვლილებების შენახვა",
+ "settingsSaved": "პარამეტრები წარმატებით შეინახა!",
+ "enterMessageTitle": "შეიყვანეთ შეტყობინების სათაური",
+ "enterMessageContent": "შეიყვანეთ თქვენი შეტყობინების შინაარსი",
+ "charactersCount": "{count}/{max} სიმბოლო"
+ },
+ "ArtistSettings": {
+ "title": "მხატვრის პროფილის პარამეტრები",
+ "description": "მართეთ თქვენი მხატვრის პროფილის ინფორმაცია",
+ "artistName": "მხატვრის სახელი",
+ "artistDescription": "მხატვრის აღწერა",
+ "enterArtistName": "შეიყვანეთ მხატვრის სახელი",
+ "enterArtistDescription": "შეიყვანეთ მხატვრის აღწერა"
+ },
+ "Errors": {
+ "failedToLoadSettings": "პარამეტრების ჩატვირთვა ვერ მოხერხდა",
+ "failedToSaveSettings": "პარამეტრების შენახვა ვერ მოხერხდა",
+ "checkAdminConfiguration": "ადმინისტრაციული კონფიგურაციის შემოწმება ვერ მოხერხდა"
+ },
+ "Email": {
+ "subject": "ადმინისტრატორის შესვლა - თქვენი დადასტურების კოდი",
+ "title": "ადმინისტრატორის შესვლის დადასტურება",
+ "codeMessage": "თქვენი დადასტურების კოდი ადმინისტრაციული წვდომისთვის არის:",
+ "expiryMessage": "ეს კოდი ვადაგასდება 10 წუთში.",
+ "ignoreMessage": "თუ ამ კოდს არ მოითხოვდით, გთხოვთ უგულებელყოთ ეს ელ-ფოსტა.",
+ "automatedMessage": "ეს არის ავტომატური შეტყობინება {siteName}-დან",
+ "textVersion": "თქვენი ადმინისტრაციული დადასტურების კოდი არის: {otp}. ეს კოდი ვადაგასდება 10 წუთში."
+ }
+}
diff --git a/messages/ui/admin/ru.json b/messages/ui/admin/ru.json
new file mode 100644
index 0000000..2c61289
--- /dev/null
+++ b/messages/ui/admin/ru.json
@@ -0,0 +1,58 @@
+{
+ "Dashboard": {
+ "title": "Панель управления",
+ "subtitle": "Управляйте настройками вашей художественной галереи",
+ "checkingConfiguration": "Проверка конфигурации…"
+ },
+ "Login": {
+ "title": "Вход",
+ "emailStep": "Введите ваш email для получения кода подтверждения",
+ "otpStep": "Введите код подтверждения, отправленный на ваш email",
+ "notAvailable": "Вход администратора недоступен",
+ "configurationRequired": "Требуется конфигурация",
+ "configurationMessage": "Вход администратора требует настройки SMTP и аутентификации. Пожалуйста, проверьте логи сервера на отсутствующие переменные окружения.",
+ "emailAddress": "Адрес электронной почты",
+ "verificationCode": "Код подтверждения",
+ "sendVerificationCode": "Отправить код подтверждения",
+ "verifyAndLogin": "Подтвердить и войти",
+ "sentTo": "Отправлено на:",
+ "useDifferentEmail": "Использовать другой email",
+ "loginSuccessful": "Вход выполнен успешно! Перенаправление…",
+ "networkError": "Ошибка сети. Пожалуйста, попробуйте снова."
+ },
+ "Settings": {
+ "broadcastMessageSettings": "Настройки широковещательного сообщения",
+ "broadcastMessageDescription": "Настройте сообщение, отображаемое в вашей галерее",
+ "displayBroadcastMessage": "Показывать широковещательное сообщение в галерее",
+ "title": "Заголовок",
+ "messageContent": "Содержание сообщения",
+ "allowDismiss": "Разрешить посетителям закрывать сообщение",
+ "saveChanges": "Сохранить изменения",
+ "settingsSaved": "Настройки успешно сохранены!",
+ "enterMessageTitle": "Введите заголовок сообщения",
+ "enterMessageContent": "Введите содержание вашего сообщения",
+ "charactersCount": "{count}/{max} символов"
+ },
+ "ArtistSettings": {
+ "title": "Настройки профиля художника",
+ "description": "Управляйте информацией вашего профиля художника",
+ "artistName": "Имя художника",
+ "artistDescription": "Описание художника",
+ "enterArtistName": "Введите имя художника",
+ "enterArtistDescription": "Введите описание художника"
+ },
+ "Errors": {
+ "failedToLoadSettings": "Не удалось загрузить настройки",
+ "failedToSaveSettings": "Не удалось сохранить настройки",
+ "checkAdminConfiguration": "Не удалось проверить конфигурацию администратора"
+ },
+ "Email": {
+ "subject": "Вход администратора - Ваш код подтверждения",
+ "title": "Подтверждение входа администратора",
+ "codeMessage": "Ваш код подтверждения для доступа администратора:",
+ "expiryMessage": "Этот код истечет через 10 минут.",
+ "ignoreMessage": "Если вы не запрашивали этот код, пожалуйста, проигнорируйте это письмо.",
+ "automatedMessage": "Это автоматическое сообщение от {siteName}",
+ "textVersion": "Ваш код подтверждения администратора: {otp}. Этот код истечет через 10 минут."
+ }
+}
diff --git a/messages/ui/admin/tr.json b/messages/ui/admin/tr.json
new file mode 100644
index 0000000..acf21d2
--- /dev/null
+++ b/messages/ui/admin/tr.json
@@ -0,0 +1,58 @@
+{
+ "Dashboard": {
+ "title": "Kontrol Paneli",
+ "subtitle": "Sanat galerinizin ayarlarını yönetin",
+ "checkingConfiguration": "Yapılandırma kontrol ediliyor…"
+ },
+ "Login": {
+ "title": "Giriş",
+ "emailStep": "Doğrulama kodu almak için e-posta adresinizi girin",
+ "otpStep": "E-postanıza gönderilen doğrulama kodunu girin",
+ "notAvailable": "Yönetici girişi mevcut değil",
+ "configurationRequired": "Yapılandırma Gerekli",
+ "configurationMessage": "Yönetici girişi SMTP ve kimlik doğrulama yapılandırması gerektirir. Lütfen eksik ortam değişkenleri için sunucu günlüklerini kontrol edin.",
+ "emailAddress": "E-posta adresi",
+ "verificationCode": "Doğrulama Kodu",
+ "sendVerificationCode": "Doğrulama Kodu Gönder",
+ "verifyAndLogin": "Doğrula ve Giriş Yap",
+ "sentTo": "Gönderildi:",
+ "useDifferentEmail": "Farklı e-posta kullan",
+ "loginSuccessful": "Giriş başarılı! Yönlendiriliyor…",
+ "networkError": "Ağ hatası. Lütfen tekrar deneyin."
+ },
+ "Settings": {
+ "broadcastMessageSettings": "Yayın Mesajı Ayarları",
+ "broadcastMessageDescription": "Galerinizde görüntülenen mesajı yapılandırın",
+ "displayBroadcastMessage": "Galeride yayın mesajını göster",
+ "title": "Başlık",
+ "messageContent": "Mesaj İçeriği",
+ "allowDismiss": "Ziyaretçilerin mesajı kapatmasına izin ver",
+ "saveChanges": "Değişiklikleri Kaydet",
+ "settingsSaved": "Ayarlar başarıyla kaydedildi!",
+ "enterMessageTitle": "Mesaj başlığını girin",
+ "enterMessageContent": "Mesaj içeriğinizi girin",
+ "charactersCount": "{count}/{max} karakter"
+ },
+ "ArtistSettings": {
+ "title": "Sanatçı Profili Ayarları",
+ "description": "Sanatçı profil bilgilerinizi yönetin",
+ "artistName": "Sanatçı Adı",
+ "artistDescription": "Sanatçı Açıklaması",
+ "enterArtistName": "Sanatçı adını girin",
+ "enterArtistDescription": "Sanatçı açıklamasını girin"
+ },
+ "Errors": {
+ "failedToLoadSettings": "Ayarlar yüklenemedi",
+ "failedToSaveSettings": "Ayarlar kaydedilemedi",
+ "checkAdminConfiguration": "Yönetici yapılandırması kontrol edilemedi"
+ },
+ "Email": {
+ "subject": "Yönetici Girişi - Doğrulama Kodunuz",
+ "title": "Yönetici Girişi Doğrulaması",
+ "codeMessage": "Yönetici erişimi için doğrulama kodunuz:",
+ "expiryMessage": "Bu kod 10 dakika içinde sona erecek.",
+ "ignoreMessage": "Bu kodu talep etmediyseniz, lütfen bu e-postayı görmezden gelin.",
+ "automatedMessage": "Bu {siteName} tarafından gönderilen otomatik bir mesajdır",
+ "textVersion": "Yönetici doğrulama kodunuz: {otp}. Bu kod 10 dakika içinde sona erecek."
+ }
+}
diff --git a/messages/ui/de.json b/messages/ui/de.json
new file mode 100644
index 0000000..31ae417
--- /dev/null
+++ b/messages/ui/de.json
@@ -0,0 +1,105 @@
+{
+ "Site": {
+ "name": "{artistName}s Kunstgalerie",
+ "description": "Multidisziplinäre Kunstsammlung",
+ "subheading": "Eine multidisziplinäre Sammlung von Kunstwerken",
+ "longDescription": "Entdecken Sie {artistName}s vielfältige Kunstsammlung mit Origami, Häkelarbeiten, Gemälden und Nagelkunst-Designs. Jedes Stück zeigt einzigartige Kreativität und Handwerkskunst."
+ },
+ "Search": {
+ "placeholder": "Kunstwerke suchen…",
+ "noResults": "Keine Kunstwerke gefunden, die Ihrer Suche entsprechen.",
+ "searchLabel": "Suchen"
+ },
+ "Artwork": {
+ "medium": "Medium",
+ "dimensions": "Abmessungen",
+ "year": "Jahr",
+ "description": "Beschreibung"
+ },
+ "Navigation": {
+ "home": "Startseite",
+ "gallery": "Galerie"
+ },
+ "greeting": {
+ "morning": "Guten Morgen!",
+ "afternoon": "Guten Tag!",
+ "evening": "Guten Abend!",
+ "default": "Hallo!"
+ },
+ "introduction": "Ich bin {name}.",
+ "ArtworkDetail": {
+ "backToGallery": "Zurück zur Galerie",
+ "backToGalleryAriaLabel": "Zurück zur Galerie gehen",
+ "artworkDetails": "Kunstwerk-Details",
+ "category": "Kategorie",
+ "viewMoreArtworks": "Weitere Kunstwerke ansehen",
+ "shareArtwork": "Kunstwerk teilen",
+ "categoryArtwork": "{category} Kunstwerk"
+ },
+ "Sort": {
+ "title": "Titel",
+ "year": "Jahr",
+ "sortByTitle": "Nach Titel sortieren",
+ "sortByYear": "Nach Jahr sortieren"
+ },
+ "Gallery": {
+ "categoryGallery": "{category} Galerie"
+ },
+ "Theme": {
+ "changeThemeColors": "Theme-Farben ändern",
+ "themeOptions": "Theme-Optionen",
+ "appearance": "Erscheinungsbild",
+ "accentColor": "Akzentfarbe",
+ "dark": "dunkel",
+ "light": "hell",
+ "colors": {
+ "pink": "Rosa",
+ "orange": "Orange",
+ "green": "Hellgrün"
+ },
+ "schemes": {
+ "light": "Hell",
+ "dark": "Dunkel",
+ "system": "System"
+ }
+ },
+ "Chat": {
+ "title": "Kunst-Assistent",
+ "placeholder": "Fragen Sie über Kunst...",
+ "emptyState": "Fragen Sie mich alles über Kunst!",
+ "openChat": "Chat öffnen",
+ "closeChat": "Chat schließen",
+ "sendMessage": "Nachricht senden",
+ "errorMessage": "Antwort konnte nicht abgerufen werden.",
+ "retry": "Wiederholen",
+ "dismissError": "Fehler ausblenden",
+ "clearChat": "Chat leeren",
+ "listening": "Hört zu…",
+ "startListening": "Nachricht diktieren",
+ "stopListening": "Diktat stoppen",
+ "recording": "Aufnahme… {time}",
+ "startRecording": "Sprachnachricht aufnehmen",
+ "stopRecording": "Aufnahme stoppen",
+ "transcribing": "Wird transkribiert…",
+ "transcriptionFailed": "Transkription fehlgeschlagen. Bitte erneut versuchen.",
+ "settings": "Einstellungen",
+ "dictationMode": "Diktat",
+ "dictationBrowser": "Browser",
+ "dictationServer": "Server (KI)",
+ "dictationBrowserUnavailable": "In diesem Browser nicht unterstützt"
+ },
+ "Common": {
+ "loading": "Wird geladen…",
+ "error": "Ein Fehler ist aufgetreten",
+ "retry": "Wiederholen",
+ "back": "Zurück",
+ "saving": "Wird gespeichert…",
+ "sending": "Wird gesendet…",
+ "verifying": "Wird bestätigt…",
+ "logout": "Abmelden",
+ "closeBroadcastMessage": "Nachricht schließen",
+ "login": "Anmelden",
+ "dashboard": "Dashboard",
+ "preview": "Vorschau"
+ }
+}
diff --git a/messages/ui/en.json b/messages/ui/en.json
new file mode 100644
index 0000000..46ce875
--- /dev/null
+++ b/messages/ui/en.json
@@ -0,0 +1,105 @@
+{
+ "Site": {
+ "name": "{artistName}'s Art Gallery",
+ "description": "Multi-disciplinary Art Collection",
+ "subheading": "A multi-disciplinary collection of artworks",
+ "longDescription": "Explore {artistName}'s diverse art collection featuring origami, crochet items, paintings, and nail art designs. Each piece showcases unique creativity and craftsmanship."
+ },
+ "Search": {
+ "placeholder": "Search artworks…",
+ "noResults": "No artworks found matching your search.",
+ "searchLabel": "Search"
+ },
+ "Artwork": {
+ "medium": "Medium",
+ "dimensions": "Dimensions",
+ "year": "Year",
+ "description": "Description"
+ },
+ "Navigation": {
+ "home": "Home",
+ "gallery": "Gallery"
+ },
+ "greeting": {
+ "morning": "Good morning!",
+ "afternoon": "Good afternoon!",
+ "evening": "Good evening!",
+ "default": "Hello!"
+ },
+ "introduction": "I'm {name}.",
+ "ArtworkDetail": {
+ "backToGallery": "Back to Gallery",
+ "backToGalleryAriaLabel": "Go back to gallery",
+ "artworkDetails": "Artwork Details",
+ "category": "Category",
+ "viewMoreArtworks": "View More Artworks",
+ "shareArtwork": "Share Artwork",
+ "categoryArtwork": "{category} Artwork"
+ },
+ "Sort": {
+ "title": "Title",
+ "year": "Year",
+ "sortByTitle": "Sort by title",
+ "sortByYear": "Sort by year"
+ },
+ "Gallery": {
+ "categoryGallery": "{category} gallery"
+ },
+ "Theme": {
+ "changeThemeColors": "Change theme colors",
+ "themeOptions": "Theme options",
+ "appearance": "Appearance",
+ "accentColor": "Accent color",
+ "dark": "dark",
+ "light": "light",
+ "colors": {
+ "pink": "Pink",
+ "orange": "Orange",
+ "green": "Light Green"
+ },
+ "schemes": {
+ "light": "Light",
+ "dark": "Dark",
+ "system": "System"
+ }
+ },
+ "Chat": {
+ "title": "Art Assistant",
+ "placeholder": "Ask about art...",
+ "emptyState": "Ask me anything about art!",
+ "openChat": "Open chat",
+ "closeChat": "Close chat",
+ "sendMessage": "Send message",
+ "errorMessage": "Unable to get a response.",
+ "retry": "Retry",
+ "dismissError": "Dismiss error",
+ "clearChat": "Clear chat",
+ "listening": "Listening…",
+ "startListening": "Dictate message",
+ "stopListening": "Stop dictation",
+ "recording": "Recording… {time}",
+ "startRecording": "Record voice message",
+ "stopRecording": "Stop recording",
+ "transcribing": "Transcribing…",
+ "transcriptionFailed": "Transcription failed. Please try again.",
+ "settings": "Settings",
+ "dictationMode": "Dictation",
+ "dictationBrowser": "Browser",
+ "dictationServer": "Server (AI)",
+ "dictationBrowserUnavailable": "Not supported in this browser"
+ },
+ "Common": {
+ "loading": "Loading…",
+ "error": "An error occurred",
+ "retry": "Retry",
+ "back": "Back",
+ "saving": "Saving…",
+ "sending": "Sending…",
+ "verifying": "Verifying…",
+ "logout": "Logout",
+ "closeBroadcastMessage": "Close broadcast message",
+ "login": "Login",
+ "dashboard": "Dashboard",
+ "preview": "Preview"
+ }
+}
diff --git a/messages/ui/es.json b/messages/ui/es.json
new file mode 100644
index 0000000..ee7c83f
--- /dev/null
+++ b/messages/ui/es.json
@@ -0,0 +1,105 @@
+{
+ "Site": {
+ "name": "Galería de Arte de {artistName}",
+ "description": "Colección de Arte Multidisciplinaria",
+ "subheading": "Una colección multidisciplinaria de obras de arte",
+ "longDescription": "Explora la diversa colección de arte de {artistName} que incluye origami, artículos de ganchillo, pinturas y diseños de arte de uñas. Cada pieza muestra creatividad única y artesanía."
+ },
+ "Search": {
+ "placeholder": "Buscar obras de arte…",
+ "noResults": "No se encontraron obras de arte que coincidan con tu búsqueda.",
+ "searchLabel": "Buscar"
+ },
+ "Artwork": {
+ "medium": "Medio",
+ "dimensions": "Dimensiones",
+ "year": "Año",
+ "description": "Descripción"
+ },
+ "Navigation": {
+ "home": "Inicio",
+ "gallery": "Galería"
+ },
+ "greeting": {
+ "morning": "¡Buenos días!",
+ "afternoon": "¡Buenas tardes!",
+ "evening": "¡Buenas noches!",
+ "default": "¡Hola!"
+ },
+ "introduction": "Soy {name}.",
+ "ArtworkDetail": {
+ "backToGallery": "Volver a la Galería",
+ "backToGalleryAriaLabel": "Volver a la galería",
+ "artworkDetails": "Detalles de la Obra",
+ "category": "Categoría",
+ "viewMoreArtworks": "Ver Más Obras",
+ "shareArtwork": "Compartir Obra",
+ "categoryArtwork": "Obra de {category}"
+ },
+ "Sort": {
+ "title": "Título",
+ "year": "Año",
+ "sortByTitle": "Ordenar por título",
+ "sortByYear": "Ordenar por año"
+ },
+ "Gallery": {
+ "categoryGallery": "galería de {category}"
+ },
+ "Theme": {
+ "changeThemeColors": "Cambiar colores del tema",
+ "themeOptions": "Opciones de tema",
+ "appearance": "Apariencia",
+ "accentColor": "Color de acento",
+ "dark": "oscuro",
+ "light": "claro",
+ "colors": {
+ "pink": "Rosa",
+ "orange": "Naranja",
+ "green": "Verde Claro"
+ },
+ "schemes": {
+ "light": "Claro",
+ "dark": "Oscuro",
+ "system": "Sistema"
+ }
+ },
+ "Chat": {
+ "title": "Asistente de Arte",
+ "placeholder": "Pregunta sobre arte...",
+ "emptyState": "¡Pregúntame lo que quieras sobre arte!",
+ "openChat": "Abrir chat",
+ "closeChat": "Cerrar chat",
+ "sendMessage": "Enviar mensaje",
+ "errorMessage": "No se pudo obtener una respuesta.",
+ "retry": "Reintentar",
+ "dismissError": "Descartar error",
+ "clearChat": "Borrar chat",
+ "listening": "Escuchando…",
+ "startListening": "Dictar mensaje",
+ "stopListening": "Detener dictado",
+ "recording": "Grabando… {time}",
+ "startRecording": "Grabar mensaje de voz",
+ "stopRecording": "Detener grabación",
+ "transcribing": "Transcribiendo…",
+ "transcriptionFailed": "Error en la transcripción. Inténtalo de nuevo.",
+ "settings": "Ajustes",
+ "dictationMode": "Dictado",
+ "dictationBrowser": "Navegador",
+ "dictationServer": "Servidor (IA)",
+ "dictationBrowserUnavailable": "No compatible con este navegador"
+ },
+ "Common": {
+ "loading": "Cargando…",
+ "error": "Ocurrió un error",
+ "retry": "Reintentar",
+ "back": "Atrás",
+ "saving": "Guardando…",
+ "sending": "Enviando…",
+ "verifying": "Verificando…",
+ "logout": "Cerrar sesión",
+ "closeBroadcastMessage": "Cerrar mensaje",
+ "login": "Iniciar sesión",
+ "dashboard": "Panel de control",
+ "preview": "Vista previa"
+ }
+}
diff --git a/messages/ui/ka.json b/messages/ui/ka.json
new file mode 100644
index 0000000..ba5137e
--- /dev/null
+++ b/messages/ui/ka.json
@@ -0,0 +1,105 @@
+{
+ "Site": {
+ "name": "{artistName}-ის ხელოვნების გალერეა",
+ "description": "მულტიდისციპლინური ხელოვნების კოლექცია",
+ "subheading": "მულტიდისციპლინური ხელოვნების ნამუშევრების კოლექცია",
+ "longDescription": "გაეცანით {artistName}-ის მრავალფეროვან ხელოვნების კოლექციას, რომელიც მოიცავს ორიგამის, ქსოვის, ნახატებისა და ფრჩხილების დიზაინის ნამუშევრებს. ყოველი ნაწარმოები გამოირჩევა უნიკალური შემოქმედებითობითა და ოსტატობით."
+ },
+ "Search": {
+ "placeholder": "ძიება ნამუშევრებში…",
+ "noResults": "თქვენს ძიებას შესაბამისი ნამუშევრები ვერ მოიძებნა.",
+ "searchLabel": "ძიება"
+ },
+ "Artwork": {
+ "medium": "მასალა",
+ "dimensions": "ზომები",
+ "year": "წელი",
+ "description": "აღწერა"
+ },
+ "Navigation": {
+ "home": "მთავარი",
+ "gallery": "გალერეა"
+ },
+ "greeting": {
+ "morning": "დილა მშვიდობისა!",
+ "afternoon": "დღე მშვიდობისა!",
+ "evening": "საღამო მშვიდობისა!",
+ "default": "გამარჯობა!"
+ },
+ "introduction": "მე ვარ {name}.",
+ "ArtworkDetail": {
+ "backToGallery": "უკან გალერეაში",
+ "backToGalleryAriaLabel": "დაბრუნება გალერეაში",
+ "artworkDetails": "ნამუშევრის დეტალები",
+ "category": "კატეგორია",
+ "viewMoreArtworks": "მეტი ნამუშევრის ნახვა",
+ "shareArtwork": "ნამუშევრის გაზიარება",
+ "categoryArtwork": "{category} ნამუშევარი"
+ },
+ "Sort": {
+ "title": "სათაური",
+ "year": "წელი",
+ "sortByTitle": "დალაგება სათაურით",
+ "sortByYear": "დალაგება წლით"
+ },
+ "Gallery": {
+ "categoryGallery": "{category} გალერეა"
+ },
+ "Theme": {
+ "changeThemeColors": "თემის ფერების შეცვლა",
+ "themeOptions": "თემის პარამეტრები",
+ "appearance": "გარეგნობა",
+ "accentColor": "აქცენტის ფერი",
+ "dark": "მუქი",
+ "light": "ღია",
+ "colors": {
+ "pink": "ვარდისფერი",
+ "orange": "ნარინჯისფერი",
+ "green": "ღია მწვანე"
+ },
+ "schemes": {
+ "light": "ღია",
+ "dark": "მუქი",
+ "system": "სისტემური"
+ }
+ },
+ "Chat": {
+ "title": "ხელოვნების ასისტენტი",
+ "placeholder": "იკითხეთ ხელოვნების შესახებ...",
+ "emptyState": "მკითხეთ ნებისმიერი რამ ხელოვნების შესახებ!",
+ "openChat": "ჩატის გახსნა",
+ "closeChat": "ჩატის დახურვა",
+ "sendMessage": "შეტყობინების გაგზავნა",
+ "errorMessage": "პასუხის მიღება ვერ მოხერხდა.",
+ "retry": "ხელახლა ცდა",
+ "dismissError": "შეცდომის დამალვა",
+ "clearChat": "ჩატის გასუფთავება",
+ "listening": "მოსმენა…",
+ "startListening": "შეტყობინების კარნახი",
+ "stopListening": "კარნახის შეჩერება",
+ "recording": "ჩაწერა… {time}",
+ "startRecording": "ხმოვანი შეტყობინების ჩაწერა",
+ "stopRecording": "ჩაწერის შეჩერება",
+ "transcribing": "ტრანსკრიბაცია…",
+ "transcriptionFailed": "ტრანსკრიბაცია ვერ მოხერხდა. სცადეთ ხელახლა.",
+ "settings": "პარამეტრები",
+ "dictationMode": "კარნახი",
+ "dictationBrowser": "ბრაუზერი",
+ "dictationServer": "სერვერი (AI)",
+ "dictationBrowserUnavailable": "ამ ბრაუზერში არ არის მხარდაჭერილი"
+ },
+ "Common": {
+ "loading": "იტვირთება…",
+ "error": "მოხდა შეცდომა",
+ "retry": "ხელახლა ცდა",
+ "back": "უკან",
+ "saving": "ინახება…",
+ "sending": "იგზავნება…",
+ "verifying": "მოწმდება…",
+ "logout": "გასვლა",
+ "closeBroadcastMessage": "შეტყობინების დახურვა",
+ "login": "შესვლა",
+ "dashboard": "მართვის პანელი",
+ "preview": "წინასწარი ხედვა"
+ }
+}
diff --git a/messages/ui/ru.json b/messages/ui/ru.json
new file mode 100644
index 0000000..0540a55
--- /dev/null
+++ b/messages/ui/ru.json
@@ -0,0 +1,105 @@
+{
+ "Site": {
+ "name": "Художественная галерея {artistName}",
+ "description": "Мультидисциплинарная коллекция искусства",
+ "subheading": "Мультидисциплинарная коллекция произведений искусства",
+ "longDescription": "Исследуйте разнообразную коллекцию искусства {artistName}, включающую оригами, вязаные изделия, картины и дизайн ногтей. Каждое произведение демонстрирует уникальное творчество и мастерство."
+ },
+ "Search": {
+ "placeholder": "Поиск произведений искусства…",
+ "noResults": "Произведения искусства, соответствующие вашему запросу, не найдены.",
+ "searchLabel": "Поиск"
+ },
+ "Artwork": {
+ "medium": "Материал",
+ "dimensions": "Размеры",
+ "year": "Год",
+ "description": "Описание"
+ },
+ "Navigation": {
+ "home": "Главная",
+ "gallery": "Галерея"
+ },
+ "greeting": {
+ "morning": "Доброе утро!",
+ "afternoon": "Добрый день!",
+ "evening": "Добрый вечер!",
+ "default": "Привет!"
+ },
+ "introduction": "Я {name}.",
+ "ArtworkDetail": {
+ "backToGallery": "Назад в галерею",
+ "backToGalleryAriaLabel": "Вернуться в галерею",
+ "artworkDetails": "Детали произведения",
+ "category": "Категория",
+ "viewMoreArtworks": "Посмотреть больше произведений",
+ "shareArtwork": "Поделиться произведением",
+ "categoryArtwork": "Произведение {category}"
+ },
+ "Sort": {
+ "title": "Название",
+ "year": "Год",
+ "sortByTitle": "Сортировать по названию",
+ "sortByYear": "Сортировать по году"
+ },
+ "Gallery": {
+ "categoryGallery": "Галерея {category}"
+ },
+ "Theme": {
+ "changeThemeColors": "Изменить цвета темы",
+ "themeOptions": "Настройки темы",
+ "appearance": "Внешний вид",
+ "accentColor": "Акцентный цвет",
+ "dark": "темная",
+ "light": "светлая",
+ "colors": {
+ "pink": "Розовый",
+ "orange": "Оранжевый",
+ "green": "Светло-зеленый"
+ },
+ "schemes": {
+ "light": "Светлая",
+ "dark": "Темная",
+ "system": "Системная"
+ }
+ },
+ "Chat": {
+ "title": "Арт-ассистент",
+ "placeholder": "Спросите об искусстве...",
+ "emptyState": "Спросите меня о чём угодно об искусстве!",
+ "openChat": "Открыть чат",
+ "closeChat": "Закрыть чат",
+ "sendMessage": "Отправить сообщение",
+ "errorMessage": "Не удалось получить ответ.",
+ "retry": "Повторить",
+ "dismissError": "Скрыть ошибку",
+ "clearChat": "Очистить чат",
+ "listening": "Слушаю…",
+ "startListening": "Надиктовать сообщение",
+ "stopListening": "Остановить диктовку",
+ "recording": "Запись… {time}",
+ "startRecording": "Записать голосовое сообщение",
+ "stopRecording": "Остановить запись",
+ "transcribing": "Транскрибирую…",
+ "transcriptionFailed": "Ошибка транскрипции. Попробуйте ещё раз.",
+ "settings": "Настройки",
+ "dictationMode": "Диктовка",
+ "dictationBrowser": "Браузер",
+ "dictationServer": "Сервер (ИИ)",
+ "dictationBrowserUnavailable": "Не поддерживается в этом браузере"
+ },
+ "Common": {
+ "loading": "Загрузка…",
+ "error": "Произошла ошибка",
+ "retry": "Повторить",
+ "back": "Назад",
+ "saving": "Сохранение…",
+ "sending": "Отправка…",
+ "verifying": "Проверка…",
+ "logout": "Выйти",
+ "closeBroadcastMessage": "Закрыть сообщение",
+ "login": "Войти",
+ "dashboard": "Панель управления",
+ "preview": "Предпросмотр"
+ }
+}
diff --git a/messages/ui/tr.json b/messages/ui/tr.json
new file mode 100644
index 0000000..884a2c2
--- /dev/null
+++ b/messages/ui/tr.json
@@ -0,0 +1,105 @@
+{
+ "Site": {
+ "name": "{artistName}'nin Sanat Galerisi",
+ "description": "Multidisipliner Sanat Koleksiyonu",
+ "subheading": "Multidisipliner sanat eserleri koleksiyonu",
+ "longDescription": "{artistName}'nin origami, örgü ürünleri, resimler ve tırnak sanatı tasarımlarını içeren çeşitli sanat koleksiyonunu keşfedin. Her eser benzersiz yaratıcılık ve ustalık sergiler."
+ },
+ "Search": {
+ "placeholder": "Sanat eserlerinde ara…",
+ "noResults": "Aramanızla eşleşen sanat eseri bulunamadı.",
+ "searchLabel": "Ara"
+ },
+ "Artwork": {
+ "medium": "Malzeme",
+ "dimensions": "Boyutlar",
+ "year": "Yıl",
+ "description": "Açıklama"
+ },
+ "Navigation": {
+ "home": "Ana Sayfa",
+ "gallery": "Galeri"
+ },
+ "greeting": {
+ "morning": "Günaydın!",
+ "afternoon": "İyi günler!",
+ "evening": "İyi akşamlar!",
+ "default": "Merhaba!"
+ },
+ "introduction": "Ben {name}.",
+ "ArtworkDetail": {
+ "backToGallery": "Galeriye Dön",
+ "backToGalleryAriaLabel": "Galeriye geri dön",
+ "artworkDetails": "Eser Detayları",
+ "category": "Kategori",
+ "viewMoreArtworks": "Daha Fazla Eser Görüntüle",
+ "shareArtwork": "Eseri Paylaş",
+ "categoryArtwork": "{category} Eseri"
+ },
+ "Sort": {
+ "title": "Başlık",
+ "year": "Yıl",
+ "sortByTitle": "Başlığa göre sırala",
+ "sortByYear": "Yıla göre sırala"
+ },
+ "Gallery": {
+ "categoryGallery": "{category} galerisi"
+ },
+ "Theme": {
+ "changeThemeColors": "Tema renklerini değiştir",
+ "themeOptions": "Tema seçenekleri",
+ "appearance": "Görünüm",
+ "accentColor": "Vurgu rengi",
+ "dark": "koyu",
+ "light": "açık",
+ "colors": {
+ "pink": "Pembe",
+ "orange": "Turuncu",
+ "green": "Açık Yeşil"
+ },
+ "schemes": {
+ "light": "Açık",
+ "dark": "Koyu",
+ "system": "Sistem"
+ }
+ },
+ "Chat": {
+ "title": "Sanat Asistanı",
+ "placeholder": "Sanat hakkında sorun...",
+ "emptyState": "Sanat hakkında istediğinizi sorun!",
+ "openChat": "Sohbeti aç",
+ "closeChat": "Sohbeti kapat",
+ "sendMessage": "Mesaj gönder",
+ "errorMessage": "Yanıt alınamadı.",
+ "retry": "Tekrar Dene",
+ "dismissError": "Hatayı kapat",
+ "clearChat": "Sohbeti temizle",
+ "listening": "Dinleniyor…",
+ "startListening": "Mesaj dikte et",
+ "stopListening": "Dikteyi durdur",
+ "recording": "Kaydediliyor… {time}",
+ "startRecording": "Sesli mesaj kaydet",
+ "stopRecording": "Kaydı durdur",
+ "transcribing": "Yazıya dökülüyor…",
+ "transcriptionFailed": "Yazıya dökme başarısız. Lütfen tekrar deneyin.",
+ "settings": "Ayarlar",
+ "dictationMode": "Dikte",
+ "dictationBrowser": "Tarayıcı",
+ "dictationServer": "Sunucu (YZ)",
+ "dictationBrowserUnavailable": "Bu tarayıcıda desteklenmiyor"
+ },
+ "Common": {
+ "loading": "Yükleniyor…",
+ "error": "Bir hata oluştu",
+ "retry": "Tekrar Dene",
+ "back": "Geri",
+ "saving": "Kaydediliyor…",
+ "sending": "Gönderiliyor…",
+ "verifying": "Doğrulanıyor…",
+ "logout": "Çıkış Yap",
+ "closeBroadcastMessage": "Mesajı kapat",
+ "login": "Giriş Yap",
+ "dashboard": "Yönetim Paneli",
+ "preview": "Önizleme"
+ }
+}
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..7673332 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,21 @@
-import type { NextConfig } from "next";
+import { NextConfig } from "next";
+import createNextIntlPlugin from "next-intl/plugin";
+const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
+
+/** @type {import('next').NextConfig} */
const nextConfig: NextConfig = {
- /* config options here */
+ images: {
+ remotePatterns: [
+ {
+ protocol: "https",
+ hostname: "source.unsplash.com",
+ port: "",
+ pathname: "/**",
+ },
+ ],
+ },
+ output: "standalone",
};
-export default nextConfig;
+export default withNextIntl(nextConfig);
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 42fc263..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,5895 +0,0 @@
-{
- "name": "nini-artgallery",
- "version": "0.1.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "nini-artgallery",
- "version": "0.1.0",
- "dependencies": {
- "next": "15.1.5",
- "react": "^19.0.0",
- "react-dom": "^19.0.0"
- },
- "devDependencies": {
- "@eslint/eslintrc": "^3",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "eslint": "^9",
- "eslint-config-next": "15.1.5",
- "postcss": "^8",
- "tailwindcss": "^3.4.1",
- "typescript": "^5"
- }
- },
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
- "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
- "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
- "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.5",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
- "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
- "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.18.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
- "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
- "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
- "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.10.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
- "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
- "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
- "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
- "cpu": [
- "arm"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
- "cpu": [
- "s390x"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
- "cpu": [
- "arm"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.0.5"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
- "cpu": [
- "s390x"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.2.0"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@next/env": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.5.tgz",
- "integrity": "sha512-jg8ygVq99W3/XXb9Y6UQsritwhjc+qeiO7QrGZRYOfviyr/HcdnhdBQu4gbp2rBIh2ZyBYTBMWbPw3JSCb0GHw==",
- "license": "MIT"
- },
- "node_modules/@next/eslint-plugin-next": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.1.5.tgz",
- "integrity": "sha512-3cCrXBybsqe94UxD6DBQCYCCiP9YohBMgZ5IzzPYHmPzj8oqNlhBii5b6o1HDDaRHdz2pVnSsAROCtrczy8O0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-glob": "3.3.1"
- }
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.5.tgz",
- "integrity": "sha512-5ttHGE75Nw9/l5S8zR2xEwR8OHEqcpPym3idIMAZ2yo+Edk0W/Vf46jGqPOZDk+m/SJ+vYZDSuztzhVha8rcdA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.5.tgz",
- "integrity": "sha512-8YnZn7vDURUUTInfOcU5l0UWplZGBqUlzvqKKUFceM11SzfNEz7E28E1Arn4/FsOf90b1Nopboy7i7ufc4jXag==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.5.tgz",
- "integrity": "sha512-rDJC4ctlYbK27tCyFUhgIv8o7miHNlpCjb2XXfTLQszwAUOSbcMN9q2y3urSrrRCyGVOd9ZR9a4S45dRh6JF3A==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.5.tgz",
- "integrity": "sha512-FG5RApf4Gu+J+pHUQxXPM81oORZrKBYKUaBTylEIQ6Lz17hKVDsLbSXInfXM0giclvXbyiLXjTv42sQMATmZ0A==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.5.tgz",
- "integrity": "sha512-NX2Ar3BCquAOYpnoYNcKz14eH03XuF7SmSlPzTSSU4PJe7+gelAjxo3Y7F2m8+hLT8ZkkqElawBp7SWBdzwqQw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.5.tgz",
- "integrity": "sha512-EQgqMiNu3mrV5eQHOIgeuh6GB5UU57tu17iFnLfBEhYfiOfyK+vleYKh2dkRVkV6ayx3eSqbIYgE7J7na4hhcA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.5.tgz",
- "integrity": "sha512-HPULzqR/VqryQZbZME8HJE3jNFmTGcp+uRMHabFbQl63TtDPm+oCXAz3q8XyGv2AoihwNApVlur9Up7rXWRcjg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.5.tgz",
- "integrity": "sha512-n74fUb/Ka1dZSVYfjwQ+nSJ+ifUff7jGurFcTuJNKZmI62FFOxQXUYit/uZXPTj2cirm1rvGWHG2GhbSol5Ikw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nolyfill/is-core-module": {
- "version": "1.0.39",
- "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
- "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.4.0"
- }
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@rtsao/scc": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
- "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@rushstack/eslint-patch": {
- "version": "1.10.5",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz",
- "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@swc/counter": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
- "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
- "license": "Apache-2.0"
- },
- "node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json5": {
- "version": "0.0.29",
- "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
- "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "20.17.14",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz",
- "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.19.2"
- }
- },
- "node_modules/@types/react": {
- "version": "19.0.7",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.7.tgz",
- "integrity": "sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.0.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.0.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz",
- "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.0.0"
- }
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz",
- "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.20.0",
- "@typescript-eslint/type-utils": "8.20.0",
- "@typescript-eslint/utils": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0",
- "graphemer": "^1.4.0",
- "ignore": "^5.3.1",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.0.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz",
- "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.20.0",
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/typescript-estree": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz",
- "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz",
- "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "8.20.0",
- "@typescript-eslint/utils": "8.20.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.0.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz",
- "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz",
- "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.0.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz",
- "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.20.0",
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/typescript-estree": "8.20.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz",
- "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.20.0",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/aria-query": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
- "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/array-buffer-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
- "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "is-array-buffer": "^3.0.5"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array-includes": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
- "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.4",
- "is-string": "^1.0.7"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.findlast": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
- "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.findlastindex": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz",
- "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.flat": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
- "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.flatmap": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
- "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/array.prototype.tosorted": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
- "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.3",
- "es-errors": "^1.3.0",
- "es-shim-unscopables": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/arraybuffer.prototype.slice": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
- "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-buffer-byte-length": "^1.0.1",
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "is-array-buffer": "^3.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/ast-types-flow": {
- "version": "0.0.8",
- "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
- "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/available-typed-arrays": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
- "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "possible-typed-array-names": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/axe-core": {
- "version": "4.10.2",
- "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz",
- "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==",
- "dev": true,
- "license": "MPL-2.0",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/axobject-query": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
- "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/busboy": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
- "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
- "dependencies": {
- "streamsearch": "^1.1.0"
- },
- "engines": {
- "node": ">=10.16.0"
- }
- },
- "node_modules/call-bind": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
- "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.0",
- "es-define-property": "^1.0.0",
- "get-intrinsic": "^1.2.4",
- "set-function-length": "^1.2.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
- "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
- "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001695",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz",
- "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/chokidar/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/client-only": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
- "license": "MIT"
- },
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
- },
- "engines": {
- "node": ">=12.5.0"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
- "node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/damerau-levenshtein": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
- "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
- "dev": true,
- "license": "BSD-2-Clause"
- },
- "node_modules/data-view-buffer": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
- "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/data-view-byte-length": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
- "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/inspect-js"
- }
- },
- "node_modules/data-view-byte-offset": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
- "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-data-view": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/define-data-property": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
- "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0",
- "es-errors": "^1.3.0",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/define-properties": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
- "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.0.1",
- "has-property-descriptors": "^1.0.0",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
- "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
- "license": "Apache-2.0",
- "optional": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/didyoumean": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/enhanced-resolve": {
- "version": "5.18.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
- "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/es-abstract": {
- "version": "1.23.9",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
- "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-buffer-byte-length": "^1.0.2",
- "arraybuffer.prototype.slice": "^1.0.4",
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "data-view-buffer": "^1.0.2",
- "data-view-byte-length": "^1.0.2",
- "data-view-byte-offset": "^1.0.1",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "es-set-tostringtag": "^2.1.0",
- "es-to-primitive": "^1.3.0",
- "function.prototype.name": "^1.1.8",
- "get-intrinsic": "^1.2.7",
- "get-proto": "^1.0.0",
- "get-symbol-description": "^1.1.0",
- "globalthis": "^1.0.4",
- "gopd": "^1.2.0",
- "has-property-descriptors": "^1.0.2",
- "has-proto": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "internal-slot": "^1.1.0",
- "is-array-buffer": "^3.0.5",
- "is-callable": "^1.2.7",
- "is-data-view": "^1.0.2",
- "is-regex": "^1.2.1",
- "is-shared-array-buffer": "^1.0.4",
- "is-string": "^1.1.1",
- "is-typed-array": "^1.1.15",
- "is-weakref": "^1.1.0",
- "math-intrinsics": "^1.1.0",
- "object-inspect": "^1.13.3",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.7",
- "own-keys": "^1.0.1",
- "regexp.prototype.flags": "^1.5.3",
- "safe-array-concat": "^1.1.3",
- "safe-push-apply": "^1.0.0",
- "safe-regex-test": "^1.1.0",
- "set-proto": "^1.0.0",
- "string.prototype.trim": "^1.2.10",
- "string.prototype.trimend": "^1.0.9",
- "string.prototype.trimstart": "^1.0.8",
- "typed-array-buffer": "^1.0.3",
- "typed-array-byte-length": "^1.0.3",
- "typed-array-byte-offset": "^1.0.4",
- "typed-array-length": "^1.0.7",
- "unbox-primitive": "^1.1.0",
- "which-typed-array": "^1.1.18"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-iterator-helpers": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
- "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.6",
- "es-errors": "^1.3.0",
- "es-set-tostringtag": "^2.0.3",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.6",
- "globalthis": "^1.0.4",
- "gopd": "^1.2.0",
- "has-property-descriptors": "^1.0.2",
- "has-proto": "^1.2.0",
- "has-symbols": "^1.1.0",
- "internal-slot": "^1.1.0",
- "iterator.prototype": "^1.1.4",
- "safe-array-concat": "^1.1.3"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-shim-unscopables": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz",
- "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.0"
- }
- },
- "node_modules/es-to-primitive": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
- "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-callable": "^1.2.7",
- "is-date-object": "^1.0.5",
- "is-symbol": "^1.0.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.18.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz",
- "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.19.0",
- "@eslint/core": "^0.10.0",
- "@eslint/eslintrc": "^3.2.0",
- "@eslint/js": "9.18.0",
- "@eslint/plugin-kit": "^0.2.5",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.1",
- "@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.2.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-config-next": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.1.5.tgz",
- "integrity": "sha512-Awm7iUJY8toOR+fU8yTxZnA7/LyOGUGOd6cENCuDfJ3gucHOSmLdOSGJ4u+nlrs8p5qXemua42bZmq+uOzxl6Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@next/eslint-plugin-next": "15.1.5",
- "@rushstack/eslint-patch": "^1.10.3",
- "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
- "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
- "eslint-import-resolver-node": "^0.3.6",
- "eslint-import-resolver-typescript": "^3.5.2",
- "eslint-plugin-import": "^2.31.0",
- "eslint-plugin-jsx-a11y": "^6.10.0",
- "eslint-plugin-react": "^7.37.0",
- "eslint-plugin-react-hooks": "^5.0.0"
- },
- "peerDependencies": {
- "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0",
- "typescript": ">=3.3.1"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-import-resolver-node": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
- "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7",
- "is-core-module": "^2.13.0",
- "resolve": "^1.22.4"
- }
- },
- "node_modules/eslint-import-resolver-node/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-import-resolver-typescript": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz",
- "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@nolyfill/is-core-module": "1.0.39",
- "debug": "^4.3.7",
- "enhanced-resolve": "^5.15.0",
- "fast-glob": "^3.3.2",
- "get-tsconfig": "^4.7.5",
- "is-bun-module": "^1.0.2",
- "is-glob": "^4.0.3",
- "stable-hash": "^0.0.4"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts"
- },
- "peerDependencies": {
- "eslint": "*",
- "eslint-plugin-import": "*",
- "eslint-plugin-import-x": "*"
- },
- "peerDependenciesMeta": {
- "eslint-plugin-import": {
- "optional": true
- },
- "eslint-plugin-import-x": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-import-resolver-typescript/node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/eslint-import-resolver-typescript/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/eslint-module-utils": {
- "version": "2.12.0",
- "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
- "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^3.2.7"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependenciesMeta": {
- "eslint": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-module-utils/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-import": {
- "version": "2.31.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
- "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@rtsao/scc": "^1.1.0",
- "array-includes": "^3.1.8",
- "array.prototype.findlastindex": "^1.2.5",
- "array.prototype.flat": "^1.3.2",
- "array.prototype.flatmap": "^1.3.2",
- "debug": "^3.2.7",
- "doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.9",
- "eslint-module-utils": "^2.12.0",
- "hasown": "^2.0.2",
- "is-core-module": "^2.15.1",
- "is-glob": "^4.0.3",
- "minimatch": "^3.1.2",
- "object.fromentries": "^2.0.8",
- "object.groupby": "^1.0.3",
- "object.values": "^1.2.0",
- "semver": "^6.3.1",
- "string.prototype.trimend": "^1.0.8",
- "tsconfig-paths": "^3.15.0"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/eslint-plugin-jsx-a11y": {
- "version": "6.10.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
- "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "aria-query": "^5.3.2",
- "array-includes": "^3.1.8",
- "array.prototype.flatmap": "^1.3.2",
- "ast-types-flow": "^0.0.8",
- "axe-core": "^4.10.0",
- "axobject-query": "^4.1.0",
- "damerau-levenshtein": "^1.0.8",
- "emoji-regex": "^9.2.2",
- "hasown": "^2.0.2",
- "jsx-ast-utils": "^3.3.5",
- "language-tags": "^1.0.9",
- "minimatch": "^3.1.2",
- "object.fromentries": "^2.0.8",
- "safe-regex-test": "^1.0.3",
- "string.prototype.includes": "^2.0.1"
- },
- "engines": {
- "node": ">=4.0"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
- }
- },
- "node_modules/eslint-plugin-react": {
- "version": "7.37.4",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz",
- "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-includes": "^3.1.8",
- "array.prototype.findlast": "^1.2.5",
- "array.prototype.flatmap": "^1.3.3",
- "array.prototype.tosorted": "^1.1.4",
- "doctrine": "^2.1.0",
- "es-iterator-helpers": "^1.2.1",
- "estraverse": "^5.3.0",
- "hasown": "^2.0.2",
- "jsx-ast-utils": "^2.4.1 || ^3.0.0",
- "minimatch": "^3.1.2",
- "object.entries": "^1.1.8",
- "object.fromentries": "^2.0.8",
- "object.values": "^1.2.1",
- "prop-types": "^15.8.1",
- "resolve": "^2.0.0-next.5",
- "semver": "^6.3.1",
- "string.prototype.matchall": "^4.0.12",
- "string.prototype.repeat": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
- "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/resolve": {
- "version": "2.0.0-next.5",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
- "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.13.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
- "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.14.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-glob": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
- "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.4"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fastq": {
- "version": "1.18.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
- "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
- "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/for-each": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
- "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-callable": "^1.1.3"
- }
- },
- "node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/function.prototype.name": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
- "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "functions-have-names": "^1.2.3",
- "hasown": "^2.0.2",
- "is-callable": "^1.2.7"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/functions-have-names": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
- "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
- "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.0",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/get-symbol-description": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
- "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-tsconfig": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
- "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve-pkg-maps": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
- }
- },
- "node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/glob/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globalthis": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
- "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-properties": "^1.2.1",
- "gopd": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/has-bigints": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
- "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/has-property-descriptors": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
- "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-define-property": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-proto": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
- "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/internal-slot": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
- "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "hasown": "^2.0.2",
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/is-array-buffer": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
- "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
- "license": "MIT",
- "optional": true
- },
- "node_modules/is-async-function": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.0.tgz",
- "integrity": "sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "get-proto": "^1.0.1",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-bigint": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
- "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-bigints": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-boolean-object": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz",
- "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-bun-module": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz",
- "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.6.3"
- }
- },
- "node_modules/is-callable": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
- "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-data-view": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
- "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "get-intrinsic": "^1.2.6",
- "is-typed-array": "^1.1.13"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-date-object": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
- "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-finalizationregistry": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
- "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-generator-function": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
- "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "get-proto": "^1.0.0",
- "has-tostringtag": "^1.0.2",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-map": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
- "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-number-object": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
- "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-regex": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
- "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-set": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
- "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-shared-array-buffer": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
- "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-string": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
- "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-symbol": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
- "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "has-symbols": "^1.1.0",
- "safe-regex-test": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-typed-array": {
- "version": "1.1.15",
- "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
- "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "which-typed-array": "^1.1.16"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakmap": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
- "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakref": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz",
- "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-weakset": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
- "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "get-intrinsic": "^1.2.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/iterator.prototype": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
- "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.6",
- "get-proto": "^1.0.0",
- "has-symbols": "^1.1.0",
- "set-function-name": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/jiti": {
- "version": "1.21.7",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
- "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jiti": "bin/jiti.js"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json5": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
- "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "minimist": "^1.2.0"
- },
- "bin": {
- "json5": "lib/cli.js"
- }
- },
- "node_modules/jsx-ast-utils": {
- "version": "3.3.5",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
- "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "object.assign": "^4.1.4",
- "object.values": "^1.1.6"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/language-subtag-registry": {
- "version": "0.3.23",
- "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
- "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/language-tags": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
- "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "language-subtag-registry": "^0.3.20"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lilconfig": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/mz": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/next": {
- "version": "15.1.5",
- "resolved": "https://registry.npmjs.org/next/-/next-15.1.5.tgz",
- "integrity": "sha512-Cf/TEegnt01hn3Hoywh6N8fvkhbOuChO4wFje24+a86wKOubgVaWkDqxGVgoWlz2Hp9luMJ9zw3epftujdnUOg==",
- "license": "MIT",
- "dependencies": {
- "@next/env": "15.1.5",
- "@swc/counter": "0.1.3",
- "@swc/helpers": "0.5.15",
- "busboy": "1.6.0",
- "caniuse-lite": "^1.0.30001579",
- "postcss": "8.4.31",
- "styled-jsx": "5.1.6"
- },
- "bin": {
- "next": "dist/bin/next"
- },
- "engines": {
- "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
- },
- "optionalDependencies": {
- "@next/swc-darwin-arm64": "15.1.5",
- "@next/swc-darwin-x64": "15.1.5",
- "@next/swc-linux-arm64-gnu": "15.1.5",
- "@next/swc-linux-arm64-musl": "15.1.5",
- "@next/swc-linux-x64-gnu": "15.1.5",
- "@next/swc-linux-x64-musl": "15.1.5",
- "@next/swc-win32-arm64-msvc": "15.1.5",
- "@next/swc-win32-x64-msvc": "15.1.5",
- "sharp": "^0.33.5"
- },
- "peerDependencies": {
- "@opentelemetry/api": "^1.1.0",
- "@playwright/test": "^1.41.2",
- "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
- }
- }
- },
- "node_modules/next/node_modules/postcss": {
- "version": "8.4.31",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
- "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.6",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.0.2"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.3",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
- "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.assign": {
- "version": "4.1.7",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
- "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0",
- "has-symbols": "^1.1.0",
- "object-keys": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.entries": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz",
- "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.fromentries": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
- "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.groupby": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
- "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/object.values": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
- "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/own-keys": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
- "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-intrinsic": "^1.2.6",
- "object-keys": "^1.1.1",
- "safe-push-apply": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/possible-typed-array-names": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
- "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.1",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
- "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.8",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-import": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
- "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/postcss-js": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
- "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase-css": "^2.0.1"
- },
- "engines": {
- "node": "^12 || ^14 || >= 16"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.4.21"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
- "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "lilconfig": "^3.0.0",
- "yaml": "^2.3.4"
- },
- "engines": {
- "node": ">= 14"
- },
- "peerDependencies": {
- "postcss": ">=8.0.9",
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "postcss": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
- }
- },
- "node_modules/postcss-nested": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
- "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^6.1.1"
- },
- "engines": {
- "node": ">=12.0"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
- "node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/react": {
- "version": "19.0.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
- "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.0.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
- "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
- "license": "MIT",
- "dependencies": {
- "scheduler": "^0.25.0"
- },
- "peerDependencies": {
- "react": "^19.0.0"
- }
- },
- "node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/read-cache": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
- "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pify": "^2.3.0"
- }
- },
- "node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/reflect.getprototypeof": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
- "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.9",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.7",
- "get-proto": "^1.0.1",
- "which-builtin-type": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/regexp.prototype.flags": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
- "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "define-properties": "^1.2.1",
- "es-errors": "^1.3.0",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "set-function-name": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.10",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.16.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/resolve-pkg-maps": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
- "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
- }
- },
- "node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/safe-array-concat": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
- "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "get-intrinsic": "^1.2.6",
- "has-symbols": "^1.1.0",
- "isarray": "^2.0.5"
- },
- "engines": {
- "node": ">=0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/safe-push-apply": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
- "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "isarray": "^2.0.5"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/safe-regex-test": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
- "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "is-regex": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/scheduler": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
- "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
- "devOptional": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/set-function-length": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
- "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.4",
- "gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/set-function-name": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
- "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-data-property": "^1.1.4",
- "es-errors": "^1.3.0",
- "functions-have-names": "^1.2.3",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/set-proto": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
- "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/sharp": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
- "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "color": "^4.2.3",
- "detect-libc": "^2.0.3",
- "semver": "^7.6.3"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.33.5",
- "@img/sharp-darwin-x64": "0.33.5",
- "@img/sharp-libvips-darwin-arm64": "1.0.4",
- "@img/sharp-libvips-darwin-x64": "1.0.4",
- "@img/sharp-libvips-linux-arm": "1.0.5",
- "@img/sharp-libvips-linux-arm64": "1.0.4",
- "@img/sharp-libvips-linux-s390x": "1.0.4",
- "@img/sharp-libvips-linux-x64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
- "@img/sharp-linux-arm": "0.33.5",
- "@img/sharp-linux-arm64": "0.33.5",
- "@img/sharp-linux-s390x": "0.33.5",
- "@img/sharp-linux-x64": "0.33.5",
- "@img/sharp-linuxmusl-arm64": "0.33.5",
- "@img/sharp-linuxmusl-x64": "0.33.5",
- "@img/sharp-wasm32": "0.33.5",
- "@img/sharp-win32-ia32": "0.33.5",
- "@img/sharp-win32-x64": "0.33.5"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/simple-swizzle": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/stable-hash": {
- "version": "0.0.4",
- "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz",
- "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/streamsearch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
- "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/string-width-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string.prototype.includes": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
- "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.3"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/string.prototype.matchall": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
- "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.6",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.0.0",
- "get-intrinsic": "^1.2.6",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "internal-slot": "^1.1.0",
- "regexp.prototype.flags": "^1.5.3",
- "set-function-name": "^2.0.2",
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.repeat": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
- "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
- }
- },
- "node_modules/string.prototype.trim": {
- "version": "1.2.10",
- "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
- "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-data-property": "^1.1.4",
- "define-properties": "^1.2.1",
- "es-abstract": "^1.23.5",
- "es-object-atoms": "^1.0.0",
- "has-property-descriptors": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimend": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
- "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.2",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/string.prototype.trimstart": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
- "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "define-properties": "^1.2.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/styled-jsx": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
- "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
- "license": "MIT",
- "dependencies": {
- "client-only": "0.0.1"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "peerDependencies": {
- "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
- }
- }
- },
- "node_modules/sucrase": {
- "version": "3.35.0",
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
- "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.2",
- "commander": "^4.0.0",
- "glob": "^10.3.10",
- "lines-and-columns": "^1.1.6",
- "mz": "^2.7.0",
- "pirates": "^4.0.1",
- "ts-interface-checker": "^0.1.9"
- },
- "bin": {
- "sucrase": "bin/sucrase",
- "sucrase-node": "bin/sucrase-node"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/tailwindcss": {
- "version": "3.4.17",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
- "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "arg": "^5.0.2",
- "chokidar": "^3.6.0",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
- "fast-glob": "^3.3.2",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "jiti": "^1.21.6",
- "lilconfig": "^3.1.3",
- "micromatch": "^4.0.8",
- "normalize-path": "^3.0.0",
- "object-hash": "^3.0.0",
- "picocolors": "^1.1.1",
- "postcss": "^8.4.47",
- "postcss-import": "^15.1.0",
- "postcss-js": "^4.0.1",
- "postcss-load-config": "^4.0.2",
- "postcss-nested": "^6.2.0",
- "postcss-selector-parser": "^6.1.2",
- "resolve": "^1.22.8",
- "sucrase": "^3.35.0"
- },
- "bin": {
- "tailwind": "lib/cli.js",
- "tailwindcss": "lib/cli.js"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tailwindcss/node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/thenify": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0"
- }
- },
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "thenify": ">= 3.1.0 < 4"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
- "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/ts-interface-checker": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/tsconfig-paths": {
- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
- "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json5": "^0.0.29",
- "json5": "^1.0.2",
- "minimist": "^1.2.6",
- "strip-bom": "^3.0.0"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/typed-array-buffer": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
- "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "es-errors": "^1.3.0",
- "is-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/typed-array-byte-length": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
- "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.14"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typed-array-byte-offset": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
- "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-proto": "^1.2.0",
- "is-typed-array": "^1.1.15",
- "reflect.getprototypeof": "^1.0.9"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typed-array-length": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
- "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.7",
- "for-each": "^0.3.3",
- "gopd": "^1.0.1",
- "is-typed-array": "^1.1.13",
- "possible-typed-array-names": "^1.0.0",
- "reflect.getprototypeof": "^1.0.6"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/typescript": {
- "version": "5.7.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
- "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/unbox-primitive": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
- "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.3",
- "has-bigints": "^1.0.2",
- "has-symbols": "^1.1.0",
- "which-boxed-primitive": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/which-boxed-primitive": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
- "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-bigint": "^1.1.0",
- "is-boolean-object": "^1.2.1",
- "is-number-object": "^1.1.1",
- "is-string": "^1.1.1",
- "is-symbol": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-builtin-type": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
- "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "function.prototype.name": "^1.1.6",
- "has-tostringtag": "^1.0.2",
- "is-async-function": "^2.0.0",
- "is-date-object": "^1.1.0",
- "is-finalizationregistry": "^1.1.0",
- "is-generator-function": "^1.0.10",
- "is-regex": "^1.2.1",
- "is-weakref": "^1.0.2",
- "isarray": "^2.0.5",
- "which-boxed-primitive": "^1.1.0",
- "which-collection": "^1.0.2",
- "which-typed-array": "^1.1.16"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-collection": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
- "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-map": "^2.0.3",
- "is-set": "^2.0.3",
- "is-weakmap": "^2.0.2",
- "is-weakset": "^2.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/which-typed-array": {
- "version": "1.1.18",
- "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz",
- "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "available-typed-arrays": "^1.0.7",
- "call-bind": "^1.0.8",
- "call-bound": "^1.0.3",
- "for-each": "^0.3.3",
- "gopd": "^1.2.0",
- "has-tostringtag": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi-cjs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/yaml": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
- "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- }
- }
-}
diff --git a/package.json b/package.json
index 49e6433..ebd9372 100644
--- a/package.json
+++ b/package.json
@@ -2,26 +2,51 @@
"name": "nini-artgallery",
"version": "0.1.0",
"private": true,
+ "packageManager": "pnpm@10.29.3",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "eslint .",
+ "lint:fix": "eslint --fix ."
},
"dependencies": {
- "react": "^19.0.0",
- "react-dom": "^19.0.0",
- "next": "15.1.5"
+ "@ai-sdk/react": "^3.0.87",
+ "@heroicons/react": "^2.2.0",
+ "@openrouter/ai-sdk-provider": "^2.2.3",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/nodemailer": "^7.0.9",
+ "ai": "^6.0.85",
+ "jsonwebtoken": "^9.0.3",
+ "next": "16.1.6",
+ "next-intl": "^4.8.2",
+ "nodemailer": "^8.0.1",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
+ "server-only": "^0.0.1",
+ "zod": "^4.3.6"
},
"devDependencies": {
- "typescript": "^5",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "postcss": "^8",
- "tailwindcss": "^3.4.1",
- "eslint": "^9",
- "eslint-config-next": "15.1.5",
- "@eslint/eslintrc": "^3"
+ "@eslint/eslintrc": "^3.3.3",
+ "@eslint/js": "^10.0.1",
+ "@tailwindcss/postcss": "^4.1.18",
+ "@types/node": "^25.2.3",
+ "@types/react": "19.2.14",
+ "@types/react-dom": "19.2.3",
+ "@typescript-eslint/eslint-plugin": "^8.55.0",
+ "@typescript-eslint/parser": "^8.55.0",
+ "eslint": "^10.0.0",
+ "eslint-config-next": "16.1.6",
+ "globals": "^17.3.0",
+ "postcss": "^8.5.6",
+ "postcss-load-config": "^6.0.1",
+ "tailwindcss": "^4.1.18",
+ "typescript": "^5.9.3"
+ },
+ "pnpm": {
+ "overrides": {
+ "@types/react": "19.2.14",
+ "@types/react-dom": "19.2.3"
+ }
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..eeb309f
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,4833 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+overrides:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3
+
+importers:
+
+ .:
+ dependencies:
+ '@ai-sdk/react':
+ specifier: ^3.0.87
+ version: 3.0.87(react@19.2.4)(zod@4.3.6)
+ '@heroicons/react':
+ specifier: ^2.2.0
+ version: 2.2.0(react@19.2.4)
+ '@openrouter/ai-sdk-provider':
+ specifier: ^2.2.3
+ version: 2.2.3(ai@6.0.85(zod@4.3.6))(zod@4.3.6)
+ '@types/jsonwebtoken':
+ specifier: ^9.0.10
+ version: 9.0.10
+ '@types/nodemailer':
+ specifier: ^7.0.9
+ version: 7.0.9
+ ai:
+ specifier: ^6.0.85
+ version: 6.0.85(zod@4.3.6)
+ jsonwebtoken:
+ specifier: ^9.0.3
+ version: 9.0.3
+ next:
+ specifier: 16.1.6
+ version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ next-intl:
+ specifier: ^4.8.2
+ version: 4.8.2(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
+ nodemailer:
+ specifier: ^8.0.1
+ version: 8.0.1
+ react:
+ specifier: 19.2.4
+ version: 19.2.4
+ react-dom:
+ specifier: 19.2.4
+ version: 19.2.4(react@19.2.4)
+ server-only:
+ specifier: ^0.0.1
+ version: 0.0.1
+ zod:
+ specifier: ^4.3.6
+ version: 4.3.6
+ devDependencies:
+ '@eslint/eslintrc':
+ specifier: ^3.3.3
+ version: 3.3.3
+ '@eslint/js':
+ specifier: ^10.0.1
+ version: 10.0.1(eslint@10.0.0(jiti@2.6.1))
+ '@tailwindcss/postcss':
+ specifier: ^4.1.18
+ version: 4.1.18
+ '@types/node':
+ specifier: ^25.2.3
+ version: 25.2.3
+ '@types/react':
+ specifier: 19.2.14
+ version: 19.2.14
+ '@types/react-dom':
+ specifier: 19.2.3
+ version: 19.2.3(@types/react@19.2.14)
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^8.55.0
+ version: 8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^8.55.0
+ version: 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ eslint:
+ specifier: ^10.0.0
+ version: 10.0.0(jiti@2.6.1)
+ eslint-config-next:
+ specifier: 16.1.6
+ version: 16.1.6(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ globals:
+ specifier: ^17.3.0
+ version: 17.3.0
+ postcss:
+ specifier: ^8.5.6
+ version: 8.5.6
+ postcss-load-config:
+ specifier: ^6.0.1
+ version: 6.0.1(jiti@2.6.1)(postcss@8.5.6)
+ tailwindcss:
+ specifier: ^4.1.18
+ version: 4.1.18
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
+
+packages:
+
+ '@ai-sdk/gateway@3.0.45':
+ resolution: {integrity: sha512-ZB6kHV+D8mLCRnkpWotLCV/rZK4NiODxx4Kv7JdT9QmQknbG/scbE4iyoT4JLFdULA8Y/IVbMvyE0Nwq3Dceqw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/provider-utils@4.0.15':
+ resolution: {integrity: sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ '@ai-sdk/provider@3.0.8':
+ resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==}
+ engines: {node: '>=18'}
+
+ '@ai-sdk/react@3.0.87':
+ resolution: {integrity: sha512-qa4Ywm08g27Voys1xuF2WeX3s8shd4hLJCCxi/Ws6cUZsWpMnFW2rtEfCcKRlWyJ4NRypauiNmcvQKz4v6u0/A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@babel/code-frame@7.29.0':
+ resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.29.0':
+ resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.29.0':
+ resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.1':
+ resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.6':
+ resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.0':
+ resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.0':
+ resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.0':
+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+ engines: {node: '>=6.9.0'}
+
+ '@emnapi/core@1.8.1':
+ resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
+
+ '@emnapi/runtime@1.8.1':
+ resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
+
+ '@emnapi/wasi-threads@1.1.0':
+ resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
+
+ '@eslint-community/eslint-utils@4.9.1':
+ resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.23.1':
+ resolution: {integrity: sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/config-helpers@0.5.2':
+ resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/core@1.1.0':
+ resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/eslintrc@3.3.3':
+ resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@10.0.1':
+ resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ peerDependencies:
+ eslint: ^10.0.0
+ peerDependenciesMeta:
+ eslint:
+ optional: true
+
+ '@eslint/object-schema@3.0.1':
+ resolution: {integrity: sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@eslint/plugin-kit@0.6.0':
+ resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ '@formatjs/ecma402-abstract@3.1.1':
+ resolution: {integrity: sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q==}
+
+ '@formatjs/fast-memoize@3.1.0':
+ resolution: {integrity: sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==}
+
+ '@formatjs/icu-messageformat-parser@3.5.1':
+ resolution: {integrity: sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA==}
+
+ '@formatjs/icu-skeleton-parser@2.1.1':
+ resolution: {integrity: sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q==}
+
+ '@formatjs/intl-localematcher@0.5.10':
+ resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
+
+ '@formatjs/intl-localematcher@0.8.1':
+ resolution: {integrity: sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==}
+
+ '@heroicons/react@2.2.0':
+ resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==}
+ peerDependencies:
+ react: '>= 16 || ^19.0.0-rc'
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.7':
+ resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@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]
+ 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]
+
+ '@isaacs/cliui@9.0.0':
+ resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
+ engines: {node: '>=18'}
+
+ '@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==}
+
+ '@napi-rs/wasm-runtime@0.2.12':
+ resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
+
+ '@next/env@16.1.6':
+ resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==}
+
+ '@next/eslint-plugin-next@16.1.6':
+ resolution: {integrity: sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==}
+
+ '@next/swc-darwin-arm64@16.1.6':
+ resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@16.1.6':
+ resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@16.1.6':
+ resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-arm64-musl@16.1.6':
+ resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-linux-x64-gnu@16.1.6':
+ resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@next/swc-linux-x64-musl@16.1.6':
+ resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@next/swc-win32-arm64-msvc@16.1.6':
+ resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@16.1.6':
+ resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@nolyfill/is-core-module@1.0.39':
+ resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
+ engines: {node: '>=12.4.0'}
+
+ '@openrouter/ai-sdk-provider@2.2.3':
+ resolution: {integrity: sha512-NovC+BaCfEeJwhToDrs8JeDYXXlJdEyz7lcxkjtyePSE4eoAKik872SyDK0MzXKcz8MRkv7XlNhPI6zz4TQp0g==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ ai: ^6.0.0
+ zod: ^3.25.0 || ^4.0.0
+
+ '@opentelemetry/api@1.9.0':
+ resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
+ engines: {node: '>=8.0.0'}
+
+ '@parcel/watcher-android-arm64@2.5.6':
+ resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.6':
+ resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.6':
+ resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.6':
+ resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.6':
+ resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-arm-musl@2.5.6':
+ resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.6':
+ resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.6':
+ resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.6':
+ resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@parcel/watcher-linux-x64-musl@2.5.6':
+ resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@parcel/watcher-win32-arm64@2.5.6':
+ resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.6':
+ resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.6':
+ resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@parcel/watcher@2.5.6':
+ resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
+ engines: {node: '>= 10.0.0'}
+
+ '@rtsao/scc@1.1.0':
+ resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+
+ '@schummar/icu-type-parser@1.21.5':
+ resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@swc/core-darwin-arm64@1.15.11':
+ resolution: {integrity: sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@swc/core-darwin-x64@1.15.11':
+ resolution: {integrity: sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@swc/core-linux-arm-gnueabihf@1.15.11':
+ resolution: {integrity: sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@swc/core-linux-arm64-gnu@1.15.11':
+ resolution: {integrity: sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-arm64-musl@1.15.11':
+ resolution: {integrity: sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@swc/core-linux-x64-gnu@1.15.11':
+ resolution: {integrity: sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@swc/core-linux-x64-musl@1.15.11':
+ resolution: {integrity: sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@swc/core-win32-arm64-msvc@1.15.11':
+ resolution: {integrity: sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@swc/core-win32-ia32-msvc@1.15.11':
+ resolution: {integrity: sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@swc/core-win32-x64-msvc@1.15.11':
+ resolution: {integrity: sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/core@1.15.11':
+ resolution: {integrity: sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@swc/helpers': '>=0.5.17'
+ peerDependenciesMeta:
+ '@swc/helpers':
+ optional: true
+
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@swc/types@0.1.25':
+ resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
+
+ '@tailwindcss/node@4.1.18':
+ resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
+ 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.18':
+ resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.18':
+ resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.18':
+ resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==}
+
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+
+ '@types/esrecurse@4.3.1':
+ resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/json5@0.0.29':
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ '@types/jsonwebtoken@9.0.10':
+ resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
+
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
+ '@types/node@25.2.3':
+ resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==}
+
+ '@types/nodemailer@7.0.9':
+ resolution: {integrity: sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': 19.2.14
+
+ '@types/react@19.2.14':
+ resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+
+ '@typescript-eslint/eslint-plugin@8.55.0':
+ resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.55.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/parser@8.55.0':
+ resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/project-service@8.55.0':
+ resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/scope-manager@8.55.0':
+ resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.55.0':
+ resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/type-utils@8.55.0':
+ resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/types@8.55.0':
+ resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.55.0':
+ resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/utils@8.55.0':
+ resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/visitor-keys@8.55.0':
+ resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
+ cpu: [arm]
+ os: [android]
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==}
+ cpu: [arm64]
+ os: [android]
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==}
+ cpu: [x64]
+ os: [win32]
+
+ '@vercel/oidc@3.1.0':
+ resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
+ engines: {node: '>= 20'}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ai@6.0.85:
+ resolution: {integrity: sha512-2bP7M+OcNQGSIH8I3jdujUadxj4tAwuHBvLhpmDSlcjRXXry3zNGEajjjRraOjObHMO/Yqa37PJWhPVHIHt2TQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4.1.8
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-includes@3.1.9:
+ resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlast@1.2.5:
+ resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlastindex@1.2.6:
+ resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.3:
+ resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.3:
+ resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.tosorted@1.1.4:
+ resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ ast-types-flow@0.0.8:
+ resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ axe-core@4.11.1:
+ resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==}
+ engines: {node: '>=4'}
+
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.2:
+ resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
+ engines: {node: 20 || >=22}
+
+ baseline-browser-mapping@2.9.19:
+ resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
+ hasBin: true
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ brace-expansion@2.0.2:
+ resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+
+ brace-expansion@5.0.2:
+ resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==}
+ engines: {node: 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ buffer-equal-constant-time@1.0.1:
+ resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.8:
+ resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ caniuse-lite@1.0.30001769:
+ resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ damerau-levenshtein@1.0.8:
+ resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ ecdsa-sig-formatter@1.0.11:
+ resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+
+ electron-to-chromium@1.5.286:
+ resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ enhanced-resolve@5.19.0:
+ resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==}
+ engines: {node: '>=10.13.0'}
+
+ es-abstract@1.24.1:
+ resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-iterator-helpers@1.2.2:
+ resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==}
+ engines: {node: '>= 0.4'}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-shim-unscopables@1.1.0:
+ resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.0:
+ resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ engines: {node: '>= 0.4'}
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-config-next@16.1.6:
+ resolution: {integrity: sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==}
+ peerDependencies:
+ eslint: '>=9.0.0'
+ typescript: '>=3.3.1'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+
+ eslint-import-resolver-typescript@3.10.1:
+ resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+ eslint-plugin-import-x: '*'
+ peerDependenciesMeta:
+ eslint-plugin-import:
+ optional: true
+ eslint-plugin-import-x:
+ optional: true
+
+ eslint-module-utils@2.12.1:
+ resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+
+ eslint-plugin-import@2.32.0:
+ resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+
+ eslint-plugin-jsx-a11y@6.10.2:
+ resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
+
+ eslint-plugin-react-hooks@7.0.1:
+ resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react@7.37.5:
+ resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+ eslint-scope@9.1.0:
+ resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@5.0.0:
+ resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ eslint@10.0.0:
+ resolution: {integrity: sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ espree@11.1.0:
+ resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ eventsource-parser@3.0.6:
+ resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
+ engines: {node: '>=18.0.0'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.1:
+ resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.1.8:
+ resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+ engines: {node: '>= 0.4'}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.13.6:
+ resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@16.4.0:
+ resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==}
+ engines: {node: '>=18'}
+
+ globals@17.3.0:
+ resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==}
+ engines: {node: '>=18'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
+ icu-minify@4.8.2:
+ resolution: {integrity: sha512-LHBQV+skKkjZSPd590pZ7ZAHftUgda3eFjeuNwA8/15L8T8loCNBktKQyTlkodAU86KovFXeg/9WntlAo5wA5A==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ ignore@7.0.5:
+ resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ intl-messageformat@11.1.2:
+ resolution: {integrity: sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg==}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-bun-module@2.0.0:
+ resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ iterator.prototype@1.1.5:
+ resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+ engines: {node: '>= 0.4'}
+
+ jackspeak@4.2.3:
+ resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
+ engines: {node: 20 || >=22}
+
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.1.1:
+ resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-schema@0.4.0:
+ resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonwebtoken@9.0.3:
+ resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
+ engines: {node: '>=12', npm: '>=6'}
+
+ jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
+
+ jwa@2.0.1:
+ resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
+
+ jws@4.0.1:
+ resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ language-subtag-registry@0.3.23:
+ resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
+
+ language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ 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]
+ libc: [glibc]
+
+ lightningcss-linux-arm64-musl@1.30.2:
+ resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-linux-x64-gnu@1.30.2:
+ resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-x64-musl@1.30.2:
+ resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ 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'}
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.includes@4.3.0:
+ resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
+
+ lodash.isboolean@3.0.3:
+ resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
+
+ lodash.isinteger@4.0.4:
+ resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
+
+ lodash.isnumber@3.0.3:
+ resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.isstring@4.0.1:
+ resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
+
+ lodash.once@4.1.1:
+ resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ minimatch@10.2.0:
+ resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==}
+ engines: {node: 20 || >=22}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ napi-postinstall@0.3.4:
+ resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ next-intl-swc-plugin-extractor@4.8.2:
+ resolution: {integrity: sha512-sHDs36L1VZmFHj3tPHsD+KZJtnsRudHlNvT0ieIe3iFVn5OpGLTxW3d/Zc/2LXSj5GpGuR6wQeikbhFjU9tMQQ==}
+
+ next-intl@4.8.2:
+ resolution: {integrity: sha512-GuuwyvyEI49/oehQbBXEoY8KSIYCzmfMLhmIwhMXTb+yeBmly1PnJcpgph3KczQ+HTJMXwXCmkizgtT8jBMf3A==}
+ peerDependencies:
+ next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+ typescript: ^5.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ next@16.1.6:
+ resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==}
+ 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
+
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ nodemailer@8.0.1:
+ resolution: {integrity: sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==}
+ engines: {node: '>=6.0.0'}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ object.entries@1.1.9:
+ resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+ engines: {node: '>= 0.4'}
+
+ object.fromentries@2.0.8:
+ resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+ engines: {node: '>= 0.4'}
+
+ object.groupby@1.0.3:
+ resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
+ engines: {node: '>= 0.4'}
+
+ object.values@1.2.1:
+ resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+ engines: {node: '>= 0.4'}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ engines: {node: '>= 0.4'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ po-parser@2.1.1:
+ resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ postcss-load-config@6.0.1:
+ resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ jiti: '>=1.21.0'
+ postcss: '>=8.0.9'
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+ postcss:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ 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}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ react-dom@19.2.4:
+ resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
+ peerDependencies:
+ react: ^19.2.4
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react@19.2.4:
+ resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
+ engines: {node: '>=0.10.0'}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ resolve@2.0.0-next.5:
+ resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
+ hasBin: true
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ safe-array-concat@1.1.3:
+ resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.4:
+ resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ server-only@0.0.1:
+ resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.0:
+ resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ stable-hash@0.0.5:
+ resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.includes@2.0.1:
+ resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.repeat@1.0.0:
+ resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+ string.prototype.trim@1.2.10:
+ resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.9:
+ resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ 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
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ swr@2.4.0:
+ resolution: {integrity: sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ tailwindcss@4.1.18:
+ resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
+
+ tapable@2.3.0:
+ resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
+ engines: {node: '>=6'}
+
+ throttleit@2.1.0:
+ resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ ts-api-utils@2.4.0:
+ resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.7:
+ resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+ engines: {node: '>= 0.4'}
+
+ typescript-eslint@8.55.0:
+ resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
+ unrs-resolver@1.11.1:
+ resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ use-intl@4.8.2:
+ resolution: {integrity: sha512-3VNXZgDnPFqhIYosQ9W1Hc6K5q+ZelMfawNbexdwL/dY7BTHbceLUBX5Eeex9lgogxTp0pf1SjHuhYNAjr9H3g==}
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-typed-array@1.1.20:
+ resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod-validation-error@4.0.2:
+ resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
+ zod@4.3.6:
+ resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+
+snapshots:
+
+ '@ai-sdk/gateway@3.0.45(zod@4.3.6)':
+ dependencies:
+ '@ai-sdk/provider': 3.0.8
+ '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6)
+ '@vercel/oidc': 3.1.0
+ zod: 4.3.6
+
+ '@ai-sdk/provider-utils@4.0.15(zod@4.3.6)':
+ dependencies:
+ '@ai-sdk/provider': 3.0.8
+ '@standard-schema/spec': 1.1.0
+ eventsource-parser: 3.0.6
+ zod: 4.3.6
+
+ '@ai-sdk/provider@3.0.8':
+ dependencies:
+ json-schema: 0.4.0
+
+ '@ai-sdk/react@3.0.87(react@19.2.4)(zod@4.3.6)':
+ dependencies:
+ '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6)
+ ai: 6.0.85(zod@4.3.6)
+ react: 19.2.4
+ swr: 2.4.0(react@19.2.4)
+ throttleit: 2.1.0
+ transitivePeerDependencies:
+ - zod
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@babel/code-frame@7.29.0':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.29.0': {}
+
+ '@babel/core@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.1':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.29.0
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.6':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+
+ '@babel/parser@7.29.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@babel/traverse@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.0':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@emnapi/core@1.8.1':
+ dependencies:
+ '@emnapi/wasi-threads': 1.1.0
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.8.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.1.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@eslint-community/eslint-utils@4.9.1(eslint@10.0.0(jiti@2.6.1))':
+ dependencies:
+ eslint: 10.0.0(jiti@2.6.1)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.23.1':
+ dependencies:
+ '@eslint/object-schema': 3.0.1
+ debug: 4.4.3
+ minimatch: 10.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.5.2':
+ dependencies:
+ '@eslint/core': 1.1.0
+
+ '@eslint/core@1.1.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.3':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@10.0.1(eslint@10.0.0(jiti@2.6.1))':
+ optionalDependencies:
+ eslint: 10.0.0(jiti@2.6.1)
+
+ '@eslint/object-schema@3.0.1': {}
+
+ '@eslint/plugin-kit@0.6.0':
+ dependencies:
+ '@eslint/core': 1.1.0
+ levn: 0.4.1
+
+ '@formatjs/ecma402-abstract@3.1.1':
+ dependencies:
+ '@formatjs/fast-memoize': 3.1.0
+ '@formatjs/intl-localematcher': 0.8.1
+ decimal.js: 10.6.0
+ tslib: 2.8.1
+
+ '@formatjs/fast-memoize@3.1.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/icu-messageformat-parser@3.5.1':
+ dependencies:
+ '@formatjs/ecma402-abstract': 3.1.1
+ '@formatjs/icu-skeleton-parser': 2.1.1
+ tslib: 2.8.1
+
+ '@formatjs/icu-skeleton-parser@2.1.1':
+ dependencies:
+ '@formatjs/ecma402-abstract': 3.1.1
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.5.10':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.8.1':
+ dependencies:
+ '@formatjs/fast-memoize': 3.1.0
+ tslib: 2.8.1
+
+ '@heroicons/react@2.2.0(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.7':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@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.8.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
+
+ '@isaacs/cliui@9.0.0': {}
+
+ '@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
+
+ '@napi-rs/wasm-runtime@0.2.12':
+ dependencies:
+ '@emnapi/core': 1.8.1
+ '@emnapi/runtime': 1.8.1
+ '@tybys/wasm-util': 0.10.1
+ optional: true
+
+ '@next/env@16.1.6': {}
+
+ '@next/eslint-plugin-next@16.1.6':
+ dependencies:
+ fast-glob: 3.3.1
+
+ '@next/swc-darwin-arm64@16.1.6':
+ optional: true
+
+ '@next/swc-darwin-x64@16.1.6':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@16.1.6':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@16.1.6':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@16.1.6':
+ optional: true
+
+ '@next/swc-linux-x64-musl@16.1.6':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@16.1.6':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@16.1.6':
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
+
+ '@nolyfill/is-core-module@1.0.39': {}
+
+ '@openrouter/ai-sdk-provider@2.2.3(ai@6.0.85(zod@4.3.6))(zod@4.3.6)':
+ dependencies:
+ ai: 6.0.85(zod@4.3.6)
+ zod: 4.3.6
+
+ '@opentelemetry/api@1.9.0': {}
+
+ '@parcel/watcher-android-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-darwin-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-freebsd-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-x64-glibc@2.5.6':
+ optional: true
+
+ '@parcel/watcher-linux-x64-musl@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-arm64@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-ia32@2.5.6':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.6':
+ optional: true
+
+ '@parcel/watcher@2.5.6':
+ dependencies:
+ detect-libc: 2.1.2
+ is-glob: 4.0.3
+ node-addon-api: 7.1.1
+ picomatch: 4.0.3
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.6
+ '@parcel/watcher-darwin-arm64': 2.5.6
+ '@parcel/watcher-darwin-x64': 2.5.6
+ '@parcel/watcher-freebsd-x64': 2.5.6
+ '@parcel/watcher-linux-arm-glibc': 2.5.6
+ '@parcel/watcher-linux-arm-musl': 2.5.6
+ '@parcel/watcher-linux-arm64-glibc': 2.5.6
+ '@parcel/watcher-linux-arm64-musl': 2.5.6
+ '@parcel/watcher-linux-x64-glibc': 2.5.6
+ '@parcel/watcher-linux-x64-musl': 2.5.6
+ '@parcel/watcher-win32-arm64': 2.5.6
+ '@parcel/watcher-win32-ia32': 2.5.6
+ '@parcel/watcher-win32-x64': 2.5.6
+
+ '@rtsao/scc@1.1.0': {}
+
+ '@schummar/icu-type-parser@1.21.5': {}
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@swc/core-darwin-arm64@1.15.11':
+ optional: true
+
+ '@swc/core-darwin-x64@1.15.11':
+ optional: true
+
+ '@swc/core-linux-arm-gnueabihf@1.15.11':
+ optional: true
+
+ '@swc/core-linux-arm64-gnu@1.15.11':
+ optional: true
+
+ '@swc/core-linux-arm64-musl@1.15.11':
+ optional: true
+
+ '@swc/core-linux-x64-gnu@1.15.11':
+ optional: true
+
+ '@swc/core-linux-x64-musl@1.15.11':
+ optional: true
+
+ '@swc/core-win32-arm64-msvc@1.15.11':
+ optional: true
+
+ '@swc/core-win32-ia32-msvc@1.15.11':
+ optional: true
+
+ '@swc/core-win32-x64-msvc@1.15.11':
+ optional: true
+
+ '@swc/core@1.15.11':
+ dependencies:
+ '@swc/counter': 0.1.3
+ '@swc/types': 0.1.25
+ optionalDependencies:
+ '@swc/core-darwin-arm64': 1.15.11
+ '@swc/core-darwin-x64': 1.15.11
+ '@swc/core-linux-arm-gnueabihf': 1.15.11
+ '@swc/core-linux-arm64-gnu': 1.15.11
+ '@swc/core-linux-arm64-musl': 1.15.11
+ '@swc/core-linux-x64-gnu': 1.15.11
+ '@swc/core-linux-x64-musl': 1.15.11
+ '@swc/core-win32-arm64-msvc': 1.15.11
+ '@swc/core-win32-ia32-msvc': 1.15.11
+ '@swc/core-win32-x64-msvc': 1.15.11
+
+ '@swc/counter@0.1.3': {}
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@swc/types@0.1.25':
+ dependencies:
+ '@swc/counter': 0.1.3
+
+ '@tailwindcss/node@4.1.18':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.19.0
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.18
+
+ '@tailwindcss/oxide-android-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.18':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.18':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-arm64': 4.1.18
+ '@tailwindcss/oxide-darwin-x64': 4.1.18
+ '@tailwindcss/oxide-freebsd-x64': 4.1.18
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.18
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.18
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.18
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.18
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+
+ '@tailwindcss/postcss@4.1.18':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.18
+ '@tailwindcss/oxide': 4.1.18
+ postcss: 8.5.6
+ tailwindcss: 4.1.18
+
+ '@tybys/wasm-util@0.10.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/esrecurse@4.3.1': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/json5@0.0.29': {}
+
+ '@types/jsonwebtoken@9.0.10':
+ dependencies:
+ '@types/ms': 2.1.0
+ '@types/node': 25.2.3
+
+ '@types/ms@2.1.0': {}
+
+ '@types/node@25.2.3':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@types/nodemailer@7.0.9':
+ dependencies:
+ '@types/node': 25.2.3
+
+ '@types/react-dom@19.2.3(@types/react@19.2.14)':
+ dependencies:
+ '@types/react': 19.2.14
+
+ '@types/react@19.2.14':
+ dependencies:
+ csstype: 3.2.3
+
+ '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.55.0
+ '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.55.0
+ eslint: 10.0.0(jiti@2.6.1)
+ ignore: 7.0.5
+ natural-compare: 1.4.0
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.55.0
+ '@typescript-eslint/types': 8.55.0
+ '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.55.0
+ debug: 4.4.3
+ eslint: 10.0.0(jiti@2.6.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.55.0
+ debug: 4.4.3
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.55.0':
+ dependencies:
+ '@typescript-eslint/types': 8.55.0
+ '@typescript-eslint/visitor-keys': 8.55.0
+
+ '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
+ '@typescript-eslint/type-utils@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.55.0
+ '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ debug: 4.4.3
+ eslint: 10.0.0(jiti@2.6.1)
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.55.0': {}
+
+ '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.55.0
+ '@typescript-eslint/visitor-keys': 8.55.0
+ debug: 4.4.3
+ minimatch: 9.0.5
+ semver: 7.7.4
+ tinyglobby: 0.2.15
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
+ '@typescript-eslint/scope-manager': 8.55.0
+ '@typescript-eslint/types': 8.55.0
+ '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
+ eslint: 10.0.0(jiti@2.6.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.55.0':
+ dependencies:
+ '@typescript-eslint/types': 8.55.0
+ eslint-visitor-keys: 4.2.1
+
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ dependencies:
+ '@napi-rs/wasm-runtime': 0.2.12
+ optional: true
+
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ optional: true
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ optional: true
+
+ '@vercel/oidc@3.1.0': {}
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ ai@6.0.85(zod@4.3.6):
+ dependencies:
+ '@ai-sdk/gateway': 3.0.45(zod@4.3.6)
+ '@ai-sdk/provider': 3.0.8
+ '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6)
+ '@opentelemetry/api': 1.9.0
+ zod: 4.3.6
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ argparse@2.0.1: {}
+
+ aria-query@5.3.2: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ array-includes@3.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ is-string: 1.1.1
+ math-intrinsics: 1.1.0
+
+ array.prototype.findlast@1.2.5:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.findlastindex@1.2.6:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flat@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flatmap@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.tosorted@1.1.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.1.0
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ ast-types-flow@0.0.8: {}
+
+ async-function@1.0.0: {}
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ axe-core@4.11.1: {}
+
+ axobject-query@4.1.0: {}
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@4.0.2:
+ dependencies:
+ jackspeak: 4.2.3
+
+ baseline-browser-mapping@2.9.19: {}
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ brace-expansion@5.0.2:
+ dependencies:
+ balanced-match: 4.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.19
+ caniuse-lite: 1.0.30001769
+ electron-to-chromium: 1.5.286
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ buffer-equal-constant-time@1.0.1: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.8:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ caniuse-lite@1.0.30001769: {}
+
+ client-only@0.0.1: {}
+
+ concat-map@0.0.1: {}
+
+ convert-source-map@2.0.0: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ csstype@3.2.3: {}
+
+ damerau-levenshtein@1.0.8: {}
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ debug@3.2.7:
+ dependencies:
+ ms: 2.1.3
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decimal.js@10.6.0: {}
+
+ deep-is@0.1.4: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ dequal@2.0.3: {}
+
+ detect-libc@2.1.2: {}
+
+ doctrine@2.1.0:
+ dependencies:
+ esutils: 2.0.3
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ ecdsa-sig-formatter@1.0.11:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ electron-to-chromium@1.5.286: {}
+
+ emoji-regex@9.2.2: {}
+
+ enhanced-resolve@5.19.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.0
+
+ es-abstract@1.24.1:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.0
+ function.prototype.name: 1.1.8
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.3
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.10
+ string.prototype.trimend: 1.0.9
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.7
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.20
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-iterator-helpers@1.2.2:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.1.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ iterator.prototype: 1.1.5
+ safe-array-concat: 1.1.3
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ es-shim-unscopables@1.1.0:
+ dependencies:
+ hasown: 2.0.2
+
+ es-to-primitive@1.3.0:
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-config-next@16.1.6(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
+ dependencies:
+ '@next/eslint-plugin-next': 16.1.6
+ eslint: 10.0.0(jiti@2.6.1)
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.0(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.0(jiti@2.6.1))
+ eslint-plugin-jsx-a11y: 6.10.2(eslint@10.0.0(jiti@2.6.1))
+ eslint-plugin-react: 7.37.5(eslint@10.0.0(jiti@2.6.1))
+ eslint-plugin-react-hooks: 7.0.1(eslint@10.0.0(jiti@2.6.1))
+ globals: 16.4.0
+ typescript-eslint: 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint-import-resolver-webpack
+ - eslint-plugin-import-x
+ - supports-color
+
+ eslint-import-resolver-node@0.3.9:
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.16.1
+ resolve: 1.22.11
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.0(jiti@2.6.1)):
+ dependencies:
+ '@nolyfill/is-core-module': 1.0.39
+ debug: 4.4.3
+ eslint: 10.0.0(jiti@2.6.1)
+ get-tsconfig: 4.13.6
+ is-bun-module: 2.0.0
+ stable-hash: 0.0.5
+ tinyglobby: 0.2.15
+ unrs-resolver: 1.11.1
+ optionalDependencies:
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.0(jiti@2.6.1))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.0(jiti@2.6.1)):
+ dependencies:
+ debug: 3.2.7
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ eslint: 10.0.0(jiti@2.6.1)
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@10.0.0(jiti@2.6.1))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.0(jiti@2.6.1)):
+ dependencies:
+ '@rtsao/scc': 1.1.0
+ array-includes: 3.1.9
+ array.prototype.findlastindex: 1.2.6
+ array.prototype.flat: 1.3.3
+ array.prototype.flatmap: 1.3.3
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 10.0.0(jiti@2.6.1)
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@10.0.0(jiti@2.6.1))
+ hasown: 2.0.2
+ is-core-module: 2.16.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.8
+ object.groupby: 1.0.3
+ object.values: 1.2.1
+ semver: 6.3.1
+ string.prototype.trimend: 1.0.9
+ tsconfig-paths: 3.15.0
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ eslint-plugin-jsx-a11y@6.10.2(eslint@10.0.0(jiti@2.6.1)):
+ dependencies:
+ aria-query: 5.3.2
+ array-includes: 3.1.9
+ array.prototype.flatmap: 1.3.3
+ ast-types-flow: 0.0.8
+ axe-core: 4.11.1
+ axobject-query: 4.1.0
+ damerau-levenshtein: 1.0.8
+ emoji-regex: 9.2.2
+ eslint: 10.0.0(jiti@2.6.1)
+ hasown: 2.0.2
+ jsx-ast-utils: 3.3.5
+ language-tags: 1.0.9
+ minimatch: 3.1.2
+ object.fromentries: 2.0.8
+ safe-regex-test: 1.1.0
+ string.prototype.includes: 2.0.1
+
+ eslint-plugin-react-hooks@7.0.1(eslint@10.0.0(jiti@2.6.1)):
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/parser': 7.29.0
+ eslint: 10.0.0(jiti@2.6.1)
+ hermes-parser: 0.25.1
+ zod: 4.3.6
+ zod-validation-error: 4.0.2(zod@4.3.6)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-react@7.37.5(eslint@10.0.0(jiti@2.6.1)):
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.findlast: 1.2.5
+ array.prototype.flatmap: 1.3.3
+ array.prototype.tosorted: 1.1.4
+ doctrine: 2.1.0
+ es-iterator-helpers: 1.2.2
+ eslint: 10.0.0(jiti@2.6.1)
+ estraverse: 5.3.0
+ hasown: 2.0.2
+ jsx-ast-utils: 3.3.5
+ minimatch: 3.1.2
+ object.entries: 1.1.9
+ object.fromentries: 2.0.8
+ object.values: 1.2.1
+ prop-types: 15.8.1
+ resolve: 2.0.0-next.5
+ semver: 6.3.1
+ string.prototype.matchall: 4.0.12
+ string.prototype.repeat: 1.0.0
+
+ eslint-scope@9.1.0:
+ dependencies:
+ '@types/esrecurse': 4.3.1
+ '@types/estree': 1.0.8
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint-visitor-keys@5.0.0: {}
+
+ eslint@10.0.0(jiti@2.6.1):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1))
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.23.1
+ '@eslint/config-helpers': 0.5.2
+ '@eslint/core': 1.1.0
+ '@eslint/plugin-kit': 0.6.0
+ '@humanfs/node': 0.16.7
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.12.6
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ escape-string-regexp: 4.0.0
+ eslint-scope: 9.1.0
+ eslint-visitor-keys: 5.0.0
+ espree: 11.1.0
+ esquery: 1.7.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ minimatch: 10.2.0
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 2.6.1
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 4.2.1
+
+ espree@11.1.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 5.0.0
+
+ esquery@1.7.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ eventsource-parser@3.0.6: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.1:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fastq@1.20.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+
+ flatted@3.3.3: {}
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.1.8:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ functions-have-names: 1.2.3
+ hasown: 2.0.2
+ is-callable: 1.2.7
+
+ functions-have-names@1.2.3: {}
+
+ generator-function@2.0.1: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ get-tsconfig@4.13.6:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ globals@14.0.0: {}
+
+ globals@16.4.0: {}
+
+ globals@17.3.0: {}
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ has-bigints@1.1.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
+ icu-minify@4.8.2:
+ dependencies:
+ '@formatjs/icu-messageformat-parser': 3.5.1
+
+ ignore@5.3.2: {}
+
+ ignore@7.0.5: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.2
+ side-channel: 1.1.0
+
+ intl-messageformat@11.1.2:
+ dependencies:
+ '@formatjs/ecma402-abstract': 3.1.1
+ '@formatjs/fast-memoize': 3.1.0
+ '@formatjs/icu-messageformat-parser': 3.5.1
+ tslib: 2.8.1
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-bun-module@2.0.0:
+ dependencies:
+ semver: 7.7.4
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-generator-function@1.1.2:
+ dependencies:
+ call-bound: 1.0.4
+ generator-function: 2.0.1
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-map@2.0.3: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.20
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ isarray@2.0.5: {}
+
+ isexe@2.0.0: {}
+
+ iterator.prototype@1.1.5:
+ dependencies:
+ define-data-property: 1.1.4
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ has-symbols: 1.1.0
+ set-function-name: 2.0.2
+
+ jackspeak@4.2.3:
+ dependencies:
+ '@isaacs/cliui': 9.0.0
+
+ jiti@2.6.1: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.1:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-schema@0.4.0: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@1.0.2:
+ dependencies:
+ minimist: 1.2.8
+
+ json5@2.2.3: {}
+
+ jsonwebtoken@9.0.3:
+ dependencies:
+ jws: 4.0.1
+ lodash.includes: 4.3.0
+ lodash.isboolean: 3.0.3
+ lodash.isinteger: 4.0.4
+ lodash.isnumber: 3.0.3
+ lodash.isplainobject: 4.0.6
+ lodash.isstring: 4.0.1
+ lodash.once: 4.1.1
+ ms: 2.1.3
+ semver: 7.7.4
+
+ jsx-ast-utils@3.3.5:
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.flat: 1.3.3
+ object.assign: 4.1.7
+ object.values: 1.2.1
+
+ jwa@2.0.1:
+ dependencies:
+ buffer-equal-constant-time: 1.0.1
+ ecdsa-sig-formatter: 1.0.11
+ safe-buffer: 5.2.1
+
+ jws@4.0.1:
+ dependencies:
+ jwa: 2.0.1
+ safe-buffer: 5.2.1
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ language-subtag-registry@0.3.23: {}
+
+ language-tags@1.0.9:
+ dependencies:
+ language-subtag-registry: 0.3.23
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ 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
+
+ lilconfig@3.1.3: {}
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.includes@4.3.0: {}
+
+ lodash.isboolean@3.0.3: {}
+
+ lodash.isinteger@4.0.4: {}
+
+ lodash.isnumber@3.0.3: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.isstring@4.0.1: {}
+
+ lodash.once@4.1.1: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ math-intrinsics@1.1.0: {}
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ minimatch@10.2.0:
+ dependencies:
+ brace-expansion: 5.0.2
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimist@1.2.8: {}
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ napi-postinstall@0.3.4: {}
+
+ natural-compare@1.4.0: {}
+
+ negotiator@1.0.0: {}
+
+ next-intl-swc-plugin-extractor@4.8.2: {}
+
+ next-intl@4.8.2(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3):
+ dependencies:
+ '@formatjs/intl-localematcher': 0.5.10
+ '@parcel/watcher': 2.5.6
+ '@swc/core': 1.15.11
+ icu-minify: 4.8.2
+ negotiator: 1.0.0
+ next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ next-intl-swc-plugin-extractor: 4.8.2
+ po-parser: 2.1.1
+ react: 19.2.4
+ use-intl: 4.8.2(react@19.2.4)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - '@swc/helpers'
+
+ next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ dependencies:
+ '@next/env': 16.1.6
+ '@swc/helpers': 0.5.15
+ baseline-browser-mapping: 2.9.19
+ caniuse-lite: 1.0.30001769
+ postcss: 8.4.31
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 16.1.6
+ '@next/swc-darwin-x64': 16.1.6
+ '@next/swc-linux-arm64-gnu': 16.1.6
+ '@next/swc-linux-arm64-musl': 16.1.6
+ '@next/swc-linux-x64-gnu': 16.1.6
+ '@next/swc-linux-x64-musl': 16.1.6
+ '@next/swc-win32-arm64-msvc': 16.1.6
+ '@next/swc-win32-x64-msvc': 16.1.6
+ '@opentelemetry/api': 1.9.0
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ node-addon-api@7.1.1: {}
+
+ node-releases@2.0.27: {}
+
+ nodemailer@8.0.1: {}
+
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ object.entries@1.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ object.fromentries@2.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+
+ object.groupby@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+
+ object.values@1.2.1:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ own-keys@1.0.1:
+ dependencies:
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.3: {}
+
+ po-parser@2.1.1: {}
+
+ possible-typed-array-names@1.1.0: {}
+
+ postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6):
+ dependencies:
+ lilconfig: 3.1.3
+ optionalDependencies:
+ jiti: 2.6.1
+ postcss: 8.5.6
+
+ 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
+
+ prelude-ls@1.2.1: {}
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ punycode@2.3.1: {}
+
+ queue-microtask@1.2.3: {}
+
+ react-dom@19.2.4(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ scheduler: 0.27.0
+
+ react-is@16.13.1: {}
+
+ react@19.2.4: {}
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ resolve-from@4.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve@1.22.11:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ resolve@2.0.0-next.5:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ reusify@1.1.0: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ safe-array-concat@1.1.3:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ semver@7.7.4: {}
+
+ server-only@0.0.1: {}
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+
+ sharp@0.34.5:
+ dependencies:
+ '@img/colour': 1.0.0
+ detect-libc: 2.1.2
+ semver: 7.7.4
+ 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
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.0
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ source-map-js@1.2.1: {}
+
+ stable-hash@0.0.5: {}
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ string.prototype.includes@2.0.1:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+
+ string.prototype.matchall@4.0.12:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ regexp.prototype.flags: 1.5.4
+ set-function-name: 2.0.2
+ side-channel: 1.1.0
+
+ string.prototype.repeat@1.0.0:
+ dependencies:
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+
+ string.prototype.trim@1.2.10:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+ has-property-descriptors: 1.0.2
+
+ string.prototype.trimend@1.0.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ strip-bom@3.0.0: {}
+
+ strip-json-comments@3.1.1: {}
+
+ styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.4
+ optionalDependencies:
+ '@babel/core': 7.29.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ swr@2.4.0(react@19.2.4):
+ dependencies:
+ dequal: 2.0.3
+ react: 19.2.4
+ use-sync-external-store: 1.6.0(react@19.2.4)
+
+ tailwindcss@4.1.18: {}
+
+ tapable@2.3.0: {}
+
+ throttleit@2.1.0: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ ts-api-utils@2.4.0(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
+ tsconfig-paths@3.15.0:
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
+ tslib@2.8.1: {}
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.7:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typescript-eslint@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
+ eslint: 10.0.0(jiti@2.6.1)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@5.9.3: {}
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ undici-types@7.16.0: {}
+
+ unrs-resolver@1.11.1:
+ dependencies:
+ napi-postinstall: 0.3.4
+ optionalDependencies:
+ '@unrs/resolver-binding-android-arm-eabi': 1.11.1
+ '@unrs/resolver-binding-android-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-arm64': 1.11.1
+ '@unrs/resolver-binding-darwin-x64': 1.11.1
+ '@unrs/resolver-binding-freebsd-x64': 1.11.1
+ '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-arm64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1
+ '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-gnu': 1.11.1
+ '@unrs/resolver-binding-linux-x64-musl': 1.11.1
+ '@unrs/resolver-binding-wasm32-wasi': 1.11.1
+ '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
+ '@unrs/resolver-binding-win32-x64-msvc': 1.11.1
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ use-intl@4.8.2(react@19.2.4):
+ dependencies:
+ '@formatjs/fast-memoize': 3.1.0
+ '@schummar/icu-type-parser': 1.21.5
+ icu-minify: 4.8.2
+ intl-messageformat: 11.1.2
+ react: 19.2.4
+
+ use-sync-external-store@1.6.0(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.1.8
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.2
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.20
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-typed-array@1.1.20:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ yallist@3.1.1: {}
+
+ yocto-queue@0.1.0: {}
+
+ zod-validation-error@4.0.2(zod@4.3.6):
+ dependencies:
+ zod: 4.3.6
+
+ zod@4.3.6: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..97babc9
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,4 @@
+onlyBuiltDependencies:
+ - "@tailwindcss/oxide"
+ - sharp
+ - unrs-resolver
diff --git a/postcss.config.mjs b/postcss.config.mjs
index 1a69fd2..79bcf13 100644
--- a/postcss.config.mjs
+++ b/postcss.config.mjs
@@ -1,7 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
- tailwindcss: {},
+ "@tailwindcss/postcss": {},
},
};
diff --git a/public/fonts/Noto_Sans/NotoSans-Italic-VariableFont_wdth,wght.ttf b/public/fonts/Noto_Sans/NotoSans-Italic-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..6245ba0
Binary files /dev/null and b/public/fonts/Noto_Sans/NotoSans-Italic-VariableFont_wdth,wght.ttf differ
diff --git a/public/fonts/Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf b/public/fonts/Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..9530d84
Binary files /dev/null and b/public/fonts/Noto_Sans/NotoSans-VariableFont_wdth,wght.ttf differ
diff --git a/public/fonts/Noto_Sans/OFL.txt b/public/fonts/Noto_Sans/OFL.txt
new file mode 100644
index 0000000..0373e14
--- /dev/null
+++ b/public/fonts/Noto_Sans/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/public/fonts/Noto_Sans/README.txt b/public/fonts/Noto_Sans/README.txt
new file mode 100644
index 0000000..4958bee
--- /dev/null
+++ b/public/fonts/Noto_Sans/README.txt
@@ -0,0 +1,136 @@
+Noto Sans Variable Font
+=======================
+
+This download contains Noto Sans as both variable fonts and static fonts.
+
+Noto Sans is a variable font with these axes:
+ wdth
+ wght
+
+This means all the styles are contained in these files:
+ NotoSans-VariableFont_wdth,wght.ttf
+ NotoSans-Italic-VariableFont_wdth,wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Noto Sans:
+ static/NotoSans_ExtraCondensed-Thin.ttf
+ static/NotoSans_ExtraCondensed-ExtraLight.ttf
+ static/NotoSans_ExtraCondensed-Light.ttf
+ static/NotoSans_ExtraCondensed-Regular.ttf
+ static/NotoSans_ExtraCondensed-Medium.ttf
+ static/NotoSans_ExtraCondensed-SemiBold.ttf
+ static/NotoSans_ExtraCondensed-Bold.ttf
+ static/NotoSans_ExtraCondensed-ExtraBold.ttf
+ static/NotoSans_ExtraCondensed-Black.ttf
+ static/NotoSans_Condensed-Thin.ttf
+ static/NotoSans_Condensed-ExtraLight.ttf
+ static/NotoSans_Condensed-Light.ttf
+ static/NotoSans_Condensed-Regular.ttf
+ static/NotoSans_Condensed-Medium.ttf
+ static/NotoSans_Condensed-SemiBold.ttf
+ static/NotoSans_Condensed-Bold.ttf
+ static/NotoSans_Condensed-ExtraBold.ttf
+ static/NotoSans_Condensed-Black.ttf
+ static/NotoSans_SemiCondensed-Thin.ttf
+ static/NotoSans_SemiCondensed-ExtraLight.ttf
+ static/NotoSans_SemiCondensed-Light.ttf
+ static/NotoSans_SemiCondensed-Regular.ttf
+ static/NotoSans_SemiCondensed-Medium.ttf
+ static/NotoSans_SemiCondensed-SemiBold.ttf
+ static/NotoSans_SemiCondensed-Bold.ttf
+ static/NotoSans_SemiCondensed-ExtraBold.ttf
+ static/NotoSans_SemiCondensed-Black.ttf
+ static/NotoSans-Thin.ttf
+ static/NotoSans-ExtraLight.ttf
+ static/NotoSans-Light.ttf
+ static/NotoSans-Regular.ttf
+ static/NotoSans-Medium.ttf
+ static/NotoSans-SemiBold.ttf
+ static/NotoSans-Bold.ttf
+ static/NotoSans-ExtraBold.ttf
+ static/NotoSans-Black.ttf
+ static/NotoSans_ExtraCondensed-ThinItalic.ttf
+ static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf
+ static/NotoSans_ExtraCondensed-LightItalic.ttf
+ static/NotoSans_ExtraCondensed-Italic.ttf
+ static/NotoSans_ExtraCondensed-MediumItalic.ttf
+ static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf
+ static/NotoSans_ExtraCondensed-BoldItalic.ttf
+ static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf
+ static/NotoSans_ExtraCondensed-BlackItalic.ttf
+ static/NotoSans_Condensed-ThinItalic.ttf
+ static/NotoSans_Condensed-ExtraLightItalic.ttf
+ static/NotoSans_Condensed-LightItalic.ttf
+ static/NotoSans_Condensed-Italic.ttf
+ static/NotoSans_Condensed-MediumItalic.ttf
+ static/NotoSans_Condensed-SemiBoldItalic.ttf
+ static/NotoSans_Condensed-BoldItalic.ttf
+ static/NotoSans_Condensed-ExtraBoldItalic.ttf
+ static/NotoSans_Condensed-BlackItalic.ttf
+ static/NotoSans_SemiCondensed-ThinItalic.ttf
+ static/NotoSans_SemiCondensed-ExtraLightItalic.ttf
+ static/NotoSans_SemiCondensed-LightItalic.ttf
+ static/NotoSans_SemiCondensed-Italic.ttf
+ static/NotoSans_SemiCondensed-MediumItalic.ttf
+ static/NotoSans_SemiCondensed-SemiBoldItalic.ttf
+ static/NotoSans_SemiCondensed-BoldItalic.ttf
+ static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf
+ static/NotoSans_SemiCondensed-BlackItalic.ttf
+ static/NotoSans-ThinItalic.ttf
+ static/NotoSans-ExtraLightItalic.ttf
+ static/NotoSans-LightItalic.ttf
+ static/NotoSans-Italic.ttf
+ static/NotoSans-MediumItalic.ttf
+ static/NotoSans-SemiBoldItalic.ttf
+ static/NotoSans-BoldItalic.ttf
+ static/NotoSans-ExtraBoldItalic.ttf
+ static/NotoSans-BlackItalic.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/public/fonts/Noto_Sans/static/NotoSans-Black.ttf b/public/fonts/Noto_Sans/static/NotoSans-Black.ttf
new file mode 100644
index 0000000..d5a6e0d
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-Black.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-BlackItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-BlackItalic.ttf
new file mode 100644
index 0000000..dfc640c
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-BlackItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-Bold.ttf b/public/fonts/Noto_Sans/static/NotoSans-Bold.ttf
new file mode 100644
index 0000000..07f0d25
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-BoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-BoldItalic.ttf
new file mode 100644
index 0000000..e538eae
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-BoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-ExtraBold.ttf b/public/fonts/Noto_Sans/static/NotoSans-ExtraBold.ttf
new file mode 100644
index 0000000..5868446
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-ExtraBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..68abd4c
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-ExtraBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-ExtraLight.ttf b/public/fonts/Noto_Sans/static/NotoSans-ExtraLight.ttf
new file mode 100644
index 0000000..078f8dc
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-ExtraLightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-ExtraLightItalic.ttf
new file mode 100644
index 0000000..acaa466
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-ExtraLightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-Italic.ttf b/public/fonts/Noto_Sans/static/NotoSans-Italic.ttf
new file mode 100644
index 0000000..d9b9e14
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-Italic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-Light.ttf b/public/fonts/Noto_Sans/static/NotoSans-Light.ttf
new file mode 100644
index 0000000..8d8a678
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-Light.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-LightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-LightItalic.ttf
new file mode 100644
index 0000000..0ab65c0
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-LightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-Medium.ttf b/public/fonts/Noto_Sans/static/NotoSans-Medium.ttf
new file mode 100644
index 0000000..a44124b
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-MediumItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-MediumItalic.ttf
new file mode 100644
index 0000000..467af1b
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-MediumItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-Regular.ttf b/public/fonts/Noto_Sans/static/NotoSans-Regular.ttf
new file mode 100644
index 0000000..4bac02f
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-SemiBold.ttf b/public/fonts/Noto_Sans/static/NotoSans-SemiBold.ttf
new file mode 100644
index 0000000..e846749
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-SemiBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-SemiBoldItalic.ttf
new file mode 100644
index 0000000..cacc7ec
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-SemiBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-Thin.ttf b/public/fonts/Noto_Sans/static/NotoSans-Thin.ttf
new file mode 100644
index 0000000..04335a5
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-Thin.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans-ThinItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans-ThinItalic.ttf
new file mode 100644
index 0000000..910dfc7
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans-ThinItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-Black.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Black.ttf
new file mode 100644
index 0000000..3186699
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Black.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-BlackItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-BlackItalic.ttf
new file mode 100644
index 0000000..d4b19bc
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-BlackItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-Bold.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Bold.ttf
new file mode 100644
index 0000000..bb82d6b
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-BoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-BoldItalic.ttf
new file mode 100644
index 0000000..c31898f
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-BoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraBold.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraBold.ttf
new file mode 100644
index 0000000..cb36919
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..7bbea17
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraLight.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraLight.ttf
new file mode 100644
index 0000000..29a7751
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraLightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraLightItalic.ttf
new file mode 100644
index 0000000..983b81a
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ExtraLightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-Italic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Italic.ttf
new file mode 100644
index 0000000..8e2d1f8
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Italic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-Light.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Light.ttf
new file mode 100644
index 0000000..32c58a5
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Light.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-LightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-LightItalic.ttf
new file mode 100644
index 0000000..c5d1b45
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-LightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-Medium.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Medium.ttf
new file mode 100644
index 0000000..45f8ea4
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-MediumItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-MediumItalic.ttf
new file mode 100644
index 0000000..92cd88a
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-MediumItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-Regular.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Regular.ttf
new file mode 100644
index 0000000..3ad9a1b
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-SemiBold.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-SemiBold.ttf
new file mode 100644
index 0000000..2f20a21
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-SemiBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-SemiBoldItalic.ttf
new file mode 100644
index 0000000..b28147d
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-SemiBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-Thin.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Thin.ttf
new file mode 100644
index 0000000..d5b50b5
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-Thin.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_Condensed-ThinItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ThinItalic.ttf
new file mode 100644
index 0000000..00d9315
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_Condensed-ThinItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Black.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Black.ttf
new file mode 100644
index 0000000..619c4f8
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Black.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-BlackItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-BlackItalic.ttf
new file mode 100644
index 0000000..f124627
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-BlackItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Bold.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Bold.ttf
new file mode 100644
index 0000000..2854b42
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-BoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-BoldItalic.ttf
new file mode 100644
index 0000000..3f6f8ac
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-BoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBold.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBold.ttf
new file mode 100644
index 0000000..2ce3cb3
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..9892967
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLight.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLight.ttf
new file mode 100644
index 0000000..ce67cb1
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf
new file mode 100644
index 0000000..45726c3
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ExtraLightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Italic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Italic.ttf
new file mode 100644
index 0000000..e6b1a73
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Italic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Light.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Light.ttf
new file mode 100644
index 0000000..5e9fef8
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Light.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-LightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-LightItalic.ttf
new file mode 100644
index 0000000..500c919
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-LightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Medium.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Medium.ttf
new file mode 100644
index 0000000..c78465e
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-MediumItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-MediumItalic.ttf
new file mode 100644
index 0000000..527291a
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-MediumItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Regular.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Regular.ttf
new file mode 100644
index 0000000..8921daa
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-SemiBold.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-SemiBold.ttf
new file mode 100644
index 0000000..83b98b2
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf
new file mode 100644
index 0000000..9dedf3e
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-SemiBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Thin.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Thin.ttf
new file mode 100644
index 0000000..81e2bf9
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-Thin.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ThinItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ThinItalic.ttf
new file mode 100644
index 0000000..17b43b1
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_ExtraCondensed-ThinItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Black.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Black.ttf
new file mode 100644
index 0000000..5a141da
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Black.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-BlackItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-BlackItalic.ttf
new file mode 100644
index 0000000..538888d
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-BlackItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Bold.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Bold.ttf
new file mode 100644
index 0000000..e0cd54f
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-BoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-BoldItalic.ttf
new file mode 100644
index 0000000..e7b743a
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-BoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraBold.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraBold.ttf
new file mode 100644
index 0000000..c50c081
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..b8b053e
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraLight.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraLight.ttf
new file mode 100644
index 0000000..6450a04
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf
new file mode 100644
index 0000000..a655d1e
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ExtraLightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Italic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Italic.ttf
new file mode 100644
index 0000000..67c7a2f
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Italic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Light.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Light.ttf
new file mode 100644
index 0000000..f9221c3
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Light.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-LightItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-LightItalic.ttf
new file mode 100644
index 0000000..9a72200
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-LightItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Medium.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Medium.ttf
new file mode 100644
index 0000000..e2c825c
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-MediumItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-MediumItalic.ttf
new file mode 100644
index 0000000..6be577a
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-MediumItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Regular.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Regular.ttf
new file mode 100644
index 0000000..06a2982
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-SemiBold.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-SemiBold.ttf
new file mode 100644
index 0000000..8c8f313
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf
new file mode 100644
index 0000000..59093a9
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-SemiBoldItalic.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Thin.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Thin.ttf
new file mode 100644
index 0000000..7d7ef33
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-Thin.ttf differ
diff --git a/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ThinItalic.ttf b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ThinItalic.ttf
new file mode 100644
index 0000000..44084d9
Binary files /dev/null and b/public/fonts/Noto_Sans/static/NotoSans_SemiCondensed-ThinItalic.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/NotoSansGeorgian-VariableFont_wdth,wght.ttf b/public/fonts/Noto_Sans_Georgian/NotoSansGeorgian-VariableFont_wdth,wght.ttf
new file mode 100644
index 0000000..266a2c0
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/NotoSansGeorgian-VariableFont_wdth,wght.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/OFL.txt b/public/fonts/Noto_Sans_Georgian/OFL.txt
new file mode 100644
index 0000000..2a6b4db
--- /dev/null
+++ b/public/fonts/Noto_Sans_Georgian/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2022 The Noto Project Authors (https://github.com/notofonts/georgian)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/public/fonts/Noto_Sans_Georgian/README.txt b/public/fonts/Noto_Sans_Georgian/README.txt
new file mode 100644
index 0000000..537892b
--- /dev/null
+++ b/public/fonts/Noto_Sans_Georgian/README.txt
@@ -0,0 +1,99 @@
+Noto Sans Georgian Variable Font
+================================
+
+This download contains Noto Sans Georgian as both a variable font and static fonts.
+
+Noto Sans Georgian is a variable font with these axes:
+ wdth
+ wght
+
+This means all the styles are contained in a single file:
+ Noto_Sans_Georgian/NotoSansGeorgian-VariableFont_wdth,wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Noto Sans Georgian:
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Thin.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraLight.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Light.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Regular.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Medium.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-SemiBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Bold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Black.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Thin.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraLight.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Light.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Regular.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Medium.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-SemiBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Bold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Black.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Thin.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraLight.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Light.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Regular.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Medium.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-SemiBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Bold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Black.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-Thin.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraLight.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-Light.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-Regular.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-Medium.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-SemiBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-Bold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraBold.ttf
+ Noto_Sans_Georgian/static/NotoSansGeorgian-Black.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Black.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Black.ttf
new file mode 100644
index 0000000..770fe42
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Black.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Bold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Bold.ttf
new file mode 100644
index 0000000..07a280d
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraBold.ttf
new file mode 100644
index 0000000..0616703
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraLight.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraLight.ttf
new file mode 100644
index 0000000..8fd7cc4
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Light.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Light.ttf
new file mode 100644
index 0000000..060e5ab
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Light.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Medium.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Medium.ttf
new file mode 100644
index 0000000..15c59cb
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Regular.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Regular.ttf
new file mode 100644
index 0000000..9735269
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-SemiBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-SemiBold.ttf
new file mode 100644
index 0000000..7c1fc5d
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Thin.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Thin.ttf
new file mode 100644
index 0000000..4d22b6b
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Thin.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Black.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Black.ttf
new file mode 100644
index 0000000..f6e21ec
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Black.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Bold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Bold.ttf
new file mode 100644
index 0000000..39987a7
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraBold.ttf
new file mode 100644
index 0000000..1ef0a9d
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraLight.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraLight.ttf
new file mode 100644
index 0000000..ad72097
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Light.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Light.ttf
new file mode 100644
index 0000000..4900074
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Light.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Medium.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Medium.ttf
new file mode 100644
index 0000000..d85a196
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Regular.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Regular.ttf
new file mode 100644
index 0000000..a8fa354
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-SemiBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-SemiBold.ttf
new file mode 100644
index 0000000..0b425de
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Thin.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Thin.ttf
new file mode 100644
index 0000000..0e01bca
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_Condensed-Thin.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Black.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Black.ttf
new file mode 100644
index 0000000..d7c3070
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Black.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Bold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Bold.ttf
new file mode 100644
index 0000000..3eb8061
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraBold.ttf
new file mode 100644
index 0000000..3054f36
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraLight.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraLight.ttf
new file mode 100644
index 0000000..43f0284
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Light.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Light.ttf
new file mode 100644
index 0000000..584f05f
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Light.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Medium.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Medium.ttf
new file mode 100644
index 0000000..2345391
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Regular.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Regular.ttf
new file mode 100644
index 0000000..ebcfd4f
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-SemiBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-SemiBold.ttf
new file mode 100644
index 0000000..32be866
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Thin.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Thin.ttf
new file mode 100644
index 0000000..c4a3930
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_ExtraCondensed-Thin.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Black.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Black.ttf
new file mode 100644
index 0000000..e3e7e21
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Black.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Bold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Bold.ttf
new file mode 100644
index 0000000..b84819d
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Bold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraBold.ttf
new file mode 100644
index 0000000..5758fa5
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraLight.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraLight.ttf
new file mode 100644
index 0000000..be034b4
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-ExtraLight.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Light.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Light.ttf
new file mode 100644
index 0000000..646afa5
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Light.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Medium.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Medium.ttf
new file mode 100644
index 0000000..839671b
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Medium.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Regular.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Regular.ttf
new file mode 100644
index 0000000..b05ddc2
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Regular.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-SemiBold.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-SemiBold.ttf
new file mode 100644
index 0000000..93b8c5f
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-SemiBold.ttf differ
diff --git a/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Thin.ttf b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Thin.ttf
new file mode 100644
index 0000000..b1c48b2
Binary files /dev/null and b/public/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian_SemiCondensed-Thin.ttf differ
diff --git a/public/sounds/message_sent.mp3 b/public/sounds/message_sent.mp3
new file mode 100644
index 0000000..bc15afa
Binary files /dev/null and b/public/sounds/message_sent.mp3 differ
diff --git a/public/sounds/new_notification.mp3 b/public/sounds/new_notification.mp3
new file mode 100644
index 0000000..4f61b6e
Binary files /dev/null and b/public/sounds/new_notification.mp3 differ
diff --git a/src/app/[locale]/admin/login/page.tsx b/src/app/[locale]/admin/login/page.tsx
new file mode 100644
index 0000000..796929c
--- /dev/null
+++ b/src/app/[locale]/admin/login/page.tsx
@@ -0,0 +1,325 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+import { useTranslations, useLocale } from "next-intl";
+
+interface LoginState {
+ step: "email" | "otp";
+ email: string;
+ otp: string;
+ loading: boolean;
+ error: string;
+ success: string;
+ configured: boolean;
+ configLoading: boolean;
+}
+
+export default function AdminLogin() {
+ const router = useRouter();
+ const locale = useLocale();
+ const t = useTranslations("admin");
+ const tCommon = useTranslations("Common");
+ const [state, setState] = useState({
+ step: "email",
+ email: "",
+ otp: "",
+ loading: false,
+ error: "",
+ success: "",
+ configured: false,
+ configLoading: true,
+ });
+
+ const updateState = (updates: Partial) => {
+ setState((prev) => ({ ...prev, ...updates }));
+ };
+
+ const checkAdminConfig = useCallback(async () => {
+ try {
+ const response = await fetch("/api/admin/config");
+ const data = await response.json();
+
+ updateState({
+ configured: data.configured,
+ configLoading: false,
+ error: "",
+ });
+ } catch {
+ updateState({
+ configured: false,
+ configLoading: false,
+ error: t("Errors.checkAdminConfiguration"),
+ });
+ }
+ }, [t]);
+
+ // Check admin configuration on component mount
+ useEffect(() => {
+ checkAdminConfig();
+ }, [checkAdminConfig]);
+
+ const handleEmailSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ updateState({ loading: true, error: "", success: "" });
+
+ try {
+ const response = await fetch("/api/admin/auth/request-otp", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email: state.email }),
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ updateState({
+ step: "otp",
+ loading: false,
+ success: data.message,
+ });
+ } else {
+ updateState({
+ loading: false,
+ error: data.message,
+ });
+ }
+ } catch {
+ updateState({
+ loading: false,
+ error: t("Login.networkError"),
+ });
+ }
+ };
+
+ const handleOTPSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ updateState({ loading: true, error: "", success: "" });
+
+ try {
+ const response = await fetch("/api/admin/auth/verify-otp", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email: state.email, otp: state.otp }),
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ updateState({
+ loading: false,
+ success: t("Login.loginSuccessful"),
+ });
+
+ // Redirect to localized admin dashboard
+ setTimeout(() => {
+ router.push(`/${locale}/admin`);
+ }, 1000);
+ } else {
+ updateState({
+ loading: false,
+ error: data.message,
+ });
+ }
+ } catch {
+ updateState({
+ loading: false,
+ error: t("Login.networkError"),
+ });
+ }
+ };
+
+ const handleBackToEmail = () => {
+ updateState({
+ step: "email",
+ otp: "",
+ error: "",
+ success: "",
+ });
+ };
+
+ if (state.configLoading) {
+ return (
+
+
+
+
+
+ {t("Dashboard.checkingConfiguration")}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {t("Login.title")}
+
+
+ {!state.configured
+ ? t("Login.notAvailable")
+ : state.step === "email"
+ ? t("Login.emailStep")
+ : t("Login.otpStep")}
+
+
+
+ {!state.configured && (
+
+
+
+
+
+ {t("Login.configurationRequired")}
+
+
+
{t("Login.configurationMessage")}
+
+
+
+
+ )}
+
+ {state.step === "email" ? (
+
+ ) : (
+
+ )}
+
+ {state.error && (
+
+ )}
+
+ {state.success && (
+
+ )}
+
+ {state.step === "otp" && (
+
+
+ {t("Login.sentTo")}{" "}
+ {state.email}
+
+
+ {t("Login.useDifferentEmail")}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/app/[locale]/admin/page.tsx b/src/app/[locale]/admin/page.tsx
new file mode 100644
index 0000000..ca31c02
--- /dev/null
+++ b/src/app/[locale]/admin/page.tsx
@@ -0,0 +1,633 @@
+"use client";
+
+import { useState, useEffect, useCallback } from "react";
+import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
+import { PersonalMessage } from "@/types/config";
+import { ArtistProfileWithTranslations } from "@/types/admin";
+import { PlusIcon, TrashIcon, GlobeAltIcon } from "@heroicons/react/24/outline";
+import SettingsCard from "@/components/admin/SettingsCard";
+import FormField from "@/components/admin/FormField";
+import SaveButton from "@/components/admin/SaveButton";
+import { getAdminLocales, getLocaleConfig } from "@/lib/locales";
+
+interface AdminState {
+ isAuthenticated: boolean;
+ loading: boolean;
+ personalMessage: PersonalMessage | null;
+ artistProfile: ArtistProfileWithTranslations | null;
+ saving: boolean;
+ savingArtist: boolean;
+ error: string;
+ success: string;
+ artistError: string;
+ artistSuccess: string;
+}
+
+// Get available locales for translation (without flags)
+const AVAILABLE_LOCALES = getAdminLocales();
+
+export default function AdminDashboard() {
+ const router = useRouter();
+ const t = useTranslations("admin");
+ const tCommon = useTranslations("Common");
+ const [state, setState] = useState({
+ isAuthenticated: false,
+ loading: true,
+ personalMessage: null,
+ artistProfile: null,
+ saving: false,
+ savingArtist: false,
+ error: "",
+ success: "",
+ artistError: "",
+ artistSuccess: "",
+ });
+
+ const updateState = (updates: Partial) => {
+ setState((prev) => ({ ...prev, ...updates }));
+ };
+
+ const loadPersonalMessage = useCallback(async () => {
+ try {
+ const response = await fetch("/api/admin/settings/personal-message");
+
+ if (response.status === 401) {
+ router.push("/admin/login");
+ return;
+ }
+
+ const data = await response.json();
+
+ if (data.success) {
+ updateState({
+ isAuthenticated: true,
+ personalMessage: data.data,
+ });
+ } else {
+ updateState({
+ error: data.message,
+ });
+ }
+ } catch {
+ updateState({
+ error: t("Errors.failedToLoadSettings"),
+ });
+ }
+ }, [router, t]);
+
+ const loadArtistProfile = useCallback(async () => {
+ try {
+ const response = await fetch("/api/admin/settings/artist");
+
+ if (response.status === 401) {
+ router.push("/admin/login");
+ return;
+ }
+
+ const data = await response.json();
+
+ if (data.success) {
+ updateState({
+ artistProfile: {
+ name: data.data.name,
+ description: data.data.description,
+ defaultLanguage: data.data.defaultLanguage || "en",
+ translations: data.data.translations || {},
+ },
+ });
+ } else {
+ updateState({
+ artistError: data.message,
+ });
+ }
+ } catch {
+ updateState({
+ artistError: t("Errors.failedToLoadSettings"),
+ });
+ }
+ }, [router, t]);
+
+ const loadAllSettings = useCallback(async () => {
+ updateState({ loading: true });
+ await Promise.all([loadPersonalMessage(), loadArtistProfile()]);
+ updateState({ loading: false });
+ }, [loadPersonalMessage, loadArtistProfile]);
+
+ // Check authentication and load data
+ useEffect(() => {
+ loadAllSettings();
+ }, [loadAllSettings]);
+
+ const handleSave = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!state.personalMessage) return;
+
+ updateState({ saving: true, error: "", success: "" });
+
+ try {
+ const response = await fetch("/api/admin/settings/personal-message", {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(state.personalMessage),
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ updateState({
+ saving: false,
+ success: t("Settings.settingsSaved"),
+ personalMessage: data.data,
+ });
+
+ // Clear success message after 3 seconds
+ setTimeout(() => {
+ updateState({ success: "" });
+ }, 3000);
+ } else {
+ updateState({
+ saving: false,
+ error: data.message,
+ });
+ }
+ } catch {
+ updateState({
+ saving: false,
+ error: t("Errors.failedToSaveSettings"),
+ });
+ }
+ };
+
+ const updatePersonalMessage = (updates: Partial) => {
+ if (!state.personalMessage) return;
+
+ updateState({
+ personalMessage: { ...state.personalMessage, ...updates },
+ });
+ };
+
+ const updateArtistProfile = (
+ updates: Partial,
+ ) => {
+ if (!state.artistProfile) return;
+
+ updateState({
+ artistProfile: { ...state.artistProfile, ...updates },
+ });
+ };
+
+ const updateArtistTranslation = (
+ locale: string,
+ field: "name" | "description",
+ value: string,
+ ) => {
+ if (!state.artistProfile) return;
+
+ const updatedTranslations = {
+ ...state.artistProfile.translations,
+ [locale]: {
+ ...state.artistProfile.translations[locale],
+ [field]: value,
+ },
+ };
+
+ updateArtistProfile({ translations: updatedTranslations });
+ };
+
+ const addTranslation = (locale: string) => {
+ if (!state.artistProfile || state.artistProfile.translations[locale])
+ return;
+
+ const updatedTranslations = {
+ ...state.artistProfile.translations,
+ [locale]: {
+ name: "",
+ description: "",
+ },
+ };
+
+ updateArtistProfile({ translations: updatedTranslations });
+ };
+
+ const removeTranslation = (locale: string) => {
+ if (!state.artistProfile || !state.artistProfile.translations[locale])
+ return;
+
+ const updatedTranslations = { ...state.artistProfile.translations };
+ delete updatedTranslations[locale];
+
+ updateArtistProfile({ translations: updatedTranslations });
+ };
+
+ const getAvailableLocalesForAdd = () => {
+ if (!state.artistProfile) return AVAILABLE_LOCALES;
+
+ return AVAILABLE_LOCALES.filter(
+ (locale) =>
+ locale.code !== state.artistProfile!.defaultLanguage &&
+ !state.artistProfile!.translations[locale.code],
+ );
+ };
+
+ const handleArtistSave = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!state.artistProfile) return;
+
+ updateState({ savingArtist: true, artistError: "", artistSuccess: "" });
+
+ try {
+ const payload = {
+ name: state.artistProfile.name,
+ description: state.artistProfile.description,
+ translations: state.artistProfile.translations,
+ };
+
+ const response = await fetch("/api/admin/settings/artist", {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ updateState({
+ savingArtist: false,
+ artistSuccess: t("Settings.settingsSaved"),
+ artistProfile: {
+ ...state.artistProfile,
+ name: data.data.name,
+ description: data.data.description,
+ translations:
+ data.data.translations || state.artistProfile.translations,
+ },
+ });
+
+ // Clear success message after 3 seconds
+ setTimeout(() => {
+ updateState({ artistSuccess: "" });
+ }, 3000);
+ } else {
+ updateState({
+ savingArtist: false,
+ artistError: data.message,
+ });
+ }
+ } catch {
+ updateState({
+ savingArtist: false,
+ artistError: t("Errors.failedToSaveSettings"),
+ });
+ }
+ };
+
+ if (state.loading) {
+ return (
+
+
+
+
+ {tCommon("loading")}
+
+
+
+ );
+ }
+
+ if (!state.isAuthenticated) {
+ return null; // Will redirect to login
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ {t("Dashboard.title")}
+
+
+ {t("Dashboard.subtitle")}
+
+
+
+
+
+
+ {/* Main Content */}
+
+
+ {/* Artist Profile Settings */}
+
+ {state.artistProfile && (
+
+ )}
+
+
+ {/* Broadcast Message Settings */}
+
+
+
+
+ );
+}
diff --git a/src/app/[locale]/artwork/[id]/page.tsx b/src/app/[locale]/artwork/[id]/page.tsx
new file mode 100644
index 0000000..b9798bf
--- /dev/null
+++ b/src/app/[locale]/artwork/[id]/page.tsx
@@ -0,0 +1,119 @@
+import { Metadata } from "next";
+import { notFound } from "next/navigation";
+import { mergeArtworksWithTranslations } from "@/lib/artworks";
+import ArtworkDetailView from "@/components/ArtworkDetailView";
+import { CategorySection, Artwork } from "@/types/artwork";
+import { getTranslations } from "next-intl/server";
+import { getSiteKeywords } from "@/lib/config";
+import { getTenantId } from "@/lib/tenant";
+
+interface ArtworkPageProps {
+ params: Promise<{ locale: string; id: string }>;
+}
+
+export async function generateMetadata({
+ params,
+}: ArtworkPageProps): Promise {
+ const { locale, id } = await params;
+ const tenantId = await getTenantId();
+
+ // For metadata generation, use getTranslations with locale
+ const t = await getTranslations({ locale });
+
+ // Create translation function compatible with mergeArtworksWithTranslations
+ const translateFn = (key: string) => {
+ try {
+ return t(key);
+ } catch {
+ // Always fail early - missing translations should never be tolerated
+ const errorMessage = `Translation missing for key: ${key}`;
+ console.error(errorMessage);
+ throw new Error(errorMessage);
+ }
+ };
+
+ // Get translated sections directly
+ const sections: CategorySection[] = await mergeArtworksWithTranslations(
+ tenantId,
+ translateFn,
+ );
+ let artwork: Artwork | null = null;
+ for (const section of sections) {
+ artwork = section.artworks.find((art: Artwork) => art.id === id) || null;
+ if (artwork) break;
+ }
+
+ if (!artwork) {
+ return {
+ title: "Artwork Not Found",
+ description: "The requested artwork could not be found.",
+ };
+ }
+
+ const siteName = t("Site.name", { artistName: t("Artist.name") });
+ const siteKeywords = await getSiteKeywords(tenantId);
+
+ return {
+ title: artwork.title,
+ description: artwork.description,
+ keywords: [artwork.title, artwork.category, artwork.medium, ...siteKeywords]
+ .filter(Boolean)
+ .join(", "),
+ openGraph: {
+ title: artwork.title,
+ description: artwork.description,
+ type: "article",
+ siteName: siteName,
+ locale: locale === "en" ? "en_US" : locale,
+ authors: [t("Artist.name")],
+ },
+ twitter: {
+ card: "summary",
+ title: artwork.title,
+ description: artwork.description,
+ },
+ authors: [{ name: t("Artist.name") }],
+ category: t(`Categories.${artwork.category}.title`),
+ };
+}
+
+// Artwork pages are fully dynamic in multi-tenant mode — tenant resolution
+// requires headers() which is incompatible with static generation.
+export const dynamic = "force-dynamic";
+
+export default async function ArtworkPage({ params }: ArtworkPageProps) {
+ const { locale, id } = await params;
+ const tenantId = await getTenantId();
+
+ // Use getTranslations with locale parameter
+ const t = await getTranslations({ locale });
+
+ // Create translation function compatible with mergeArtworksWithTranslations
+ const translateFn = (key: string) => {
+ try {
+ return t(key);
+ } catch {
+ // Always fail early - missing translations should never be tolerated
+ const errorMessage = `Translation missing for key: ${key}`;
+ console.error(errorMessage);
+ throw new Error(errorMessage);
+ }
+ };
+
+ // Get translated sections directly
+ const sections: CategorySection[] = await mergeArtworksWithTranslations(
+ tenantId,
+ translateFn,
+ );
+ let artwork: Artwork | null = null;
+ for (const section of sections) {
+ artwork = section.artworks.find((art: Artwork) => art.id === id) || null;
+ if (artwork) break;
+ }
+
+ if (!artwork) {
+ notFound();
+ }
+
+ return ;
+}
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
new file mode 100644
index 0000000..5309389
--- /dev/null
+++ b/src/app/[locale]/layout.tsx
@@ -0,0 +1,108 @@
+import type { Metadata } from "next";
+import { notFound } from "next/navigation";
+import { NextIntlClientProvider } from "next-intl";
+import { getMessages, getTranslations } from "next-intl/server";
+import { SUPPORTED_LOCALES } from "@/lib/locales";
+import { ThemeProvider } from "@/components/theme/ThemeProvider";
+import ThemeSelector from "@/components/theme/ThemeSelector";
+import ChatToggle from "@/components/ChatToggle";
+import Header from "@/components/Header";
+import LocaleHtmlLang from "@/components/LocaleHtmlLang";
+import { Suspense } from "react";
+import { getSiteKeywords } from "@/lib/config";
+import { getTenantId } from "@/lib/tenant";
+
+interface LocaleLayoutProps {
+ children: React.ReactNode;
+ params: Promise<{ locale: string }>;
+}
+
+// Generate metadata using translations with locale
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>;
+}): Promise {
+ const { locale } = await params;
+ const t = await getTranslations({ locale });
+ const tenantId = await getTenantId();
+ const siteName = t("Site.name", { artistName: t("Artist.name") });
+ const siteDescription = t("Site.description");
+
+ return {
+ title: {
+ template: `%s | ${siteName}`,
+ default: `${siteName} | ${siteDescription}`,
+ },
+ description: t("Site.longDescription", { artistName: t("Artist.name") }),
+ keywords: await getSiteKeywords(tenantId),
+ robots: {
+ index: true,
+ follow: true,
+ },
+ openGraph: {
+ type: "website",
+ locale: locale === "en" ? "en_US" : locale,
+ title: siteName,
+ description: siteDescription,
+ siteName: siteName,
+ },
+ twitter: {
+ card: "summary",
+ title: siteName,
+ description: siteDescription,
+ },
+ authors: [{ name: t("Artist.name") }],
+ creator: t("Artist.name"),
+ category: "art gallery",
+ alternates: {
+ languages: Object.fromEntries(
+ SUPPORTED_LOCALES.map((loc) => [loc.code, `/${loc.code}`]),
+ ),
+ },
+ };
+}
+
+export default async function LocaleLayout({
+ children,
+ params,
+}: LocaleLayoutProps) {
+ const { locale } = await params;
+
+ // Validate that the incoming `locale` parameter is valid
+ const isValidLocale = SUPPORTED_LOCALES.some(
+ (supportedLocale) => supportedLocale.code === locale,
+ );
+
+ if (!isValidLocale) {
+ notFound();
+ }
+
+ // Providing all messages to the client side is the easiest way to get started
+ const messages = await getMessages({ locale });
+
+ return (
+
+
+
+ }>
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
+export function generateStaticParams() {
+ return SUPPORTED_LOCALES.map((locale) => ({ locale: locale.code }));
+}
diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx
new file mode 100644
index 0000000..5ba6c49
--- /dev/null
+++ b/src/app/[locale]/page.tsx
@@ -0,0 +1,98 @@
+import { Metadata } from "next";
+import GalleryContainer from "@/components/GalleryContainer";
+import { getTranslations } from "next-intl/server";
+import { getArtworks, mergeArtworksWithTranslations } from "@/lib/artworks";
+import { getSiteKeywords, getPersonalMessage } from "@/lib/config";
+import { getTenantId } from "@/lib/tenant";
+
+interface HomeProps {
+ params: Promise<{ locale: string }>;
+ searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
+}
+
+// Generate metadata using translations
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>;
+}): Promise {
+ const { locale } = await params;
+ const t = await getTranslations({ locale });
+ const tenantId = await getTenantId();
+
+ return {
+ title: `${t("Site.name", { artistName: t("Artist.name") })} | ${t("Site.description")}`,
+ description: t("Site.longDescription", { artistName: t("Artist.name") }),
+ keywords: await getSiteKeywords(tenantId),
+ };
+}
+
+// Mark the page as dynamic to ensure it's not statically optimized
+export const dynamic = "force-dynamic";
+// Disable caching for this route
+export const revalidate = 0;
+
+export default async function Home({ params, searchParams }: HomeProps) {
+ const { locale } = await params;
+ const searchParamsResolved = await searchParams;
+ const tenantId = await getTenantId();
+
+ const search =
+ typeof searchParamsResolved.search === "string"
+ ? searchParamsResolved.search
+ : Array.isArray(searchParamsResolved.search)
+ ? searchParamsResolved.search[0]
+ : undefined;
+
+ const sortBy =
+ typeof searchParamsResolved.sortBy === "string"
+ ? searchParamsResolved.sortBy
+ : undefined;
+ const order =
+ typeof searchParamsResolved.order === "string"
+ ? (searchParamsResolved.order as "asc" | "desc")
+ : "asc";
+ const category =
+ typeof searchParamsResolved.category === "string"
+ ? searchParamsResolved.category
+ : undefined;
+
+ // Use getTranslations with locale parameter
+ const t = await getTranslations({ locale });
+
+ // Create translation function compatible with mergeArtworksWithTranslations
+ const translateFn = (key: string) => {
+ try {
+ return t(key);
+ } catch {
+ // Always fail early - missing translations should never be tolerated
+ const errorMessage = `Translation missing for key: ${key}`;
+ console.error(errorMessage);
+ throw new Error(errorMessage);
+ }
+ };
+
+ // Get translated sections directly without API call
+ const translatedSections = await mergeArtworksWithTranslations(
+ tenantId,
+ translateFn,
+ );
+
+ // Apply filters and sorting
+ const categorySections = await getArtworks(translatedSections, {
+ category,
+ search,
+ sortBy,
+ order,
+ });
+
+ // Load personal message server-side and pass as prop (client component can't use fs)
+ const personalMessage = await getPersonalMessage(tenantId);
+
+ return (
+
+ );
+}
diff --git a/src/app/api/admin/auth/logout/route.ts b/src/app/api/admin/auth/logout/route.ts
new file mode 100644
index 0000000..1e6d439
--- /dev/null
+++ b/src/app/api/admin/auth/logout/route.ts
@@ -0,0 +1,28 @@
+import { NextResponse } from "next/server";
+
+export async function POST() {
+ try {
+ // Create response
+ const response = NextResponse.json({
+ success: true,
+ message: "Logged out successfully",
+ });
+
+ // Clear the admin token cookie
+ response.cookies.set("admin-token", "", {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 0, // Expire immediately
+ path: "/",
+ });
+
+ return response;
+ } catch (error) {
+ console.error("Logout error:", error);
+ return NextResponse.json(
+ { success: false, message: "Internal server error" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/admin/auth/request-otp/route.ts b/src/app/api/admin/auth/request-otp/route.ts
new file mode 100644
index 0000000..0cb39ba
--- /dev/null
+++ b/src/app/api/admin/auth/request-otp/route.ts
@@ -0,0 +1,99 @@
+import { NextRequest, NextResponse } from "next/server";
+import { cookies } from "next/headers";
+import {
+ isAdminConfigured,
+ getMissingEnvVars,
+ isAuthorizedEmail,
+ checkRateLimit,
+ generateOTP,
+ sendOTPEmail,
+ storeOTPSession,
+} from "@/lib/admin-auth";
+import { OTPRequest } from "@/types/admin";
+
+export const runtime = "nodejs";
+
+export async function POST(request: NextRequest) {
+ try {
+ // Check if admin is properly configured
+ if (!isAdminConfigured()) {
+ const missing = getMissingEnvVars();
+ console.error(
+ "Admin login not configured. Missing environment variables:",
+ missing,
+ );
+ return NextResponse.json(
+ {
+ success: false,
+ message:
+ "Admin login is not configured. Please check server configuration.",
+ },
+ { status: 503 },
+ );
+ }
+
+ const body: OTPRequest = await request.json();
+ const { email } = body;
+
+ // Validate input
+ if (!email || typeof email !== "string") {
+ return NextResponse.json(
+ { success: false, message: "Valid email is required" },
+ { status: 400 },
+ );
+ }
+
+ // Check if email is authorized for admin access
+ if (!isAuthorizedEmail(email)) {
+ return NextResponse.json(
+ { success: false, message: "Unauthorized email address" },
+ { status: 403 },
+ );
+ }
+
+ // Check rate limiting
+ if (!checkRateLimit(email)) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: "Too many requests. Please try again in 15 minutes.",
+ },
+ { status: 429 },
+ );
+ }
+
+ // Generate OTP
+ const otp = generateOTP();
+
+ // Get locale from cookies for localized email
+ const cookieStore = await cookies();
+ const locale = cookieStore.get("locale")?.value || "en";
+
+ // Send OTP email
+ const emailSent = await sendOTPEmail(email, otp, locale);
+
+ if (!emailSent) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: "Failed to send verification email. Please try again.",
+ },
+ { status: 500 },
+ );
+ }
+
+ // Store OTP session
+ storeOTPSession(email, otp);
+
+ return NextResponse.json({
+ success: true,
+ message: "Verification code sent to your email address",
+ });
+ } catch (error) {
+ console.error("OTP request error:", error);
+ return NextResponse.json(
+ { success: false, message: "Internal server error" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/admin/auth/session/route.ts b/src/app/api/admin/auth/session/route.ts
new file mode 100644
index 0000000..e95709b
--- /dev/null
+++ b/src/app/api/admin/auth/session/route.ts
@@ -0,0 +1,33 @@
+import { NextRequest, NextResponse } from "next/server";
+import { verifyAdminToken } from "@/lib/admin-auth";
+
+export const runtime = "nodejs";
+
+export async function GET(request: NextRequest) {
+ try {
+ const cookieToken = request.cookies.get("admin-token")?.value;
+
+ if (!cookieToken) {
+ return NextResponse.json({
+ authenticated: false,
+ });
+ }
+
+ const session = verifyAdminToken(cookieToken);
+
+ if (!session) {
+ return NextResponse.json({
+ authenticated: false,
+ });
+ }
+
+ return NextResponse.json({
+ authenticated: true,
+ email: session.email,
+ });
+ } catch {
+ return NextResponse.json({
+ authenticated: false,
+ });
+ }
+}
diff --git a/src/app/api/admin/auth/verify-otp/route.ts b/src/app/api/admin/auth/verify-otp/route.ts
new file mode 100644
index 0000000..e378e26
--- /dev/null
+++ b/src/app/api/admin/auth/verify-otp/route.ts
@@ -0,0 +1,74 @@
+import { NextRequest, NextResponse } from "next/server";
+import {
+ verifyOTP,
+ generateAdminToken,
+ isAuthorizedEmail,
+} from "@/lib/admin-auth";
+import { OTPVerification } from "@/types/admin";
+
+export const runtime = "nodejs";
+
+export async function POST(request: NextRequest) {
+ try {
+ const body: OTPVerification = await request.json();
+ const { email, otp } = body;
+
+ // Validate input
+ if (
+ !email ||
+ typeof email !== "string" ||
+ !otp ||
+ typeof otp !== "string"
+ ) {
+ return NextResponse.json(
+ { success: false, message: "Email and OTP are required" },
+ { status: 400 },
+ );
+ }
+
+ // Check if email is authorized for admin access
+ if (!isAuthorizedEmail(email)) {
+ return NextResponse.json(
+ { success: false, message: "Unauthorized email address" },
+ { status: 403 },
+ );
+ }
+
+ // Verify OTP
+ const isValidOTP = verifyOTP(email, otp);
+
+ if (!isValidOTP) {
+ return NextResponse.json(
+ { success: false, message: "Invalid or expired verification code" },
+ { status: 401 },
+ );
+ }
+
+ // Generate admin session token
+ const token = generateAdminToken(email);
+
+ // Set secure HTTP-only cookie
+ const response = NextResponse.json({
+ success: true,
+ message: "Authentication successful",
+ token,
+ });
+
+ // Set cookie with security flags
+ response.cookies.set("admin-token", token, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "strict",
+ maxAge: 24 * 60 * 60, // 24 hours in seconds
+ path: "/",
+ });
+
+ return response;
+ } catch (error) {
+ console.error("OTP verification error:", error);
+ return NextResponse.json(
+ { success: false, message: "Internal server error" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/admin/config/route.ts b/src/app/api/admin/config/route.ts
new file mode 100644
index 0000000..a7fbb4c
--- /dev/null
+++ b/src/app/api/admin/config/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from "next/server";
+import { isAdminConfigured, getMissingEnvVars } from "@/lib/admin-auth";
+
+export const runtime = "nodejs";
+
+export async function GET() {
+ try {
+ const configured = isAdminConfigured();
+
+ if (!configured) {
+ const missing = getMissingEnvVars();
+ console.warn(
+ "Admin not configured. Missing environment variables:",
+ missing,
+ );
+ }
+
+ return NextResponse.json({
+ configured,
+ message: configured
+ ? "Admin is properly configured"
+ : "Admin login requires environment configuration",
+ });
+ } catch (error) {
+ console.error("Config check error:", error);
+ return NextResponse.json(
+ { configured: false, message: "Configuration check failed" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/admin/settings/artist/route.ts b/src/app/api/admin/settings/artist/route.ts
new file mode 100644
index 0000000..f6b17ca
--- /dev/null
+++ b/src/app/api/admin/settings/artist/route.ts
@@ -0,0 +1,234 @@
+import { NextRequest, NextResponse } from "next/server";
+import { verifyAdminToken } from "@/lib/admin-auth";
+import { ArtistTranslations } from "@/types/admin";
+import {
+ readUserArtistData,
+ writeUserArtistData,
+ readUserArtistTranslations,
+ writeUserArtistTranslations,
+ clearArtistDataCache,
+} from "@/lib/artist-data-server";
+import { getTenantIdFromRequest } from "@/lib/tenant";
+
+export const runtime = "nodejs";
+
+// Helper function to verify admin authentication
+function getAdminSession(request: NextRequest) {
+ // Try to get token from cookie first, then from Authorization header
+ const cookieToken = request.cookies.get("admin-token")?.value;
+ const authHeader = request.headers.get("authorization");
+ const headerToken = authHeader?.startsWith("Bearer ")
+ ? authHeader.slice(7)
+ : null;
+
+ const token = cookieToken || headerToken;
+
+ console.log("Auth debug:", {
+ hasCookie: !!cookieToken,
+ hasHeader: !!headerToken,
+ hasToken: !!token,
+ cookieValue: cookieToken ? "present" : "missing",
+ });
+
+ if (!token) {
+ console.log("No token found");
+ return null;
+ }
+
+ try {
+ const result = verifyAdminToken(token);
+ console.log("Token verification result:", result ? "valid" : "invalid");
+ return result;
+ } catch (error) {
+ console.error("Token verification error:", error);
+ return null;
+ }
+}
+
+export async function GET(request: NextRequest) {
+ try {
+ // Verify admin authentication
+ const adminSession = getAdminSession(request);
+ if (!adminSession) {
+ return NextResponse.json(
+ { success: false, message: "Unauthorized" },
+ { status: 401 },
+ );
+ }
+
+ // Resolve tenant
+ const tenantId = await getTenantIdFromRequest(request);
+ if (!tenantId) {
+ return NextResponse.json(
+ { success: false, message: "Unknown tenant" },
+ { status: 404 },
+ );
+ }
+
+ // Read current artist data and translations
+ const [artistData, artistTranslations] = await Promise.all([
+ readUserArtistData(tenantId),
+ readUserArtistTranslations(tenantId),
+ ]);
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ name: artistData.name,
+ description: artistData.description,
+ defaultLanguage: artistData.defaultLanguage,
+ translations: artistTranslations,
+ },
+ });
+ } catch (error) {
+ console.error("Error reading artist data:", error);
+ return NextResponse.json(
+ { success: false, message: "Failed to read artist data" },
+ { status: 500 },
+ );
+ }
+}
+
+export async function PUT(request: NextRequest) {
+ try {
+ // Verify admin authentication
+ const adminSession = getAdminSession(request);
+ if (!adminSession) {
+ return NextResponse.json(
+ { success: false, message: "Unauthorized" },
+ { status: 401 },
+ );
+ }
+
+ // Resolve tenant
+ const tenantId = await getTenantIdFromRequest(request);
+ if (!tenantId) {
+ return NextResponse.json(
+ { success: false, message: "Unknown tenant" },
+ { status: 404 },
+ );
+ }
+
+ const body = await request.json();
+ const { name, description, translations } = body;
+
+ // Validate primary data
+ if (typeof name !== "string" || typeof description !== "string") {
+ return NextResponse.json(
+ { success: false, message: "Invalid input data" },
+ { status: 400 },
+ );
+ }
+
+ // Validate string lengths for primary data
+ if (name.length > 100) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: "Artist name too long (max 100 characters)",
+ },
+ { status: 400 },
+ );
+ }
+
+ if (description.length > 1000) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: "Artist description too long (max 1000 characters)",
+ },
+ { status: 400 },
+ );
+ }
+
+ // Validate translations if provided
+ if (translations && typeof translations === "object") {
+ for (const [locale, translation] of Object.entries(translations)) {
+ if (typeof translation !== "object" || !translation) continue;
+
+ const { name: transName, description: transDesc } = translation as {
+ name: string;
+ description: string;
+ };
+
+ if (typeof transName !== "string" || typeof transDesc !== "string") {
+ return NextResponse.json(
+ {
+ success: false,
+ message: `Invalid translation data for locale: ${locale}`,
+ },
+ { status: 400 },
+ );
+ }
+
+ if (transName.length > 100) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: `Translation name too long for ${locale} (max 100 characters)`,
+ },
+ { status: 400 },
+ );
+ }
+
+ if (transDesc.length > 1000) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: `Translation description too long for ${locale} (max 1000 characters)`,
+ },
+ { status: 400 },
+ );
+ }
+ }
+ }
+
+ // Create updated artist object
+ const updatedArtist = {
+ name: name.trim(),
+ description: description.trim(),
+ };
+
+ // Write primary data to tenant-scoped artist.json
+ await writeUserArtistData(tenantId, updatedArtist);
+
+ // Write translations if provided
+ if (translations && typeof translations === "object") {
+ // Clean up translations (trim strings)
+ const cleanTranslations: ArtistTranslations = {};
+ for (const [locale, translation] of Object.entries(translations)) {
+ if (typeof translation === "object" && translation) {
+ const { name: transName, description: transDesc } = translation as {
+ name: string;
+ description: string;
+ };
+ if (typeof transName === "string" && typeof transDesc === "string") {
+ cleanTranslations[locale] = {
+ name: transName.trim(),
+ description: transDesc.trim(),
+ };
+ }
+ }
+ }
+ await writeUserArtistTranslations(tenantId, cleanTranslations);
+ }
+
+ // Clear cache to ensure fresh data on next request
+ clearArtistDataCache(tenantId);
+
+ return NextResponse.json({
+ success: true,
+ message: "Artist profile updated successfully",
+ data: {
+ ...updatedArtist,
+ translations: translations || {},
+ },
+ });
+ } catch (error) {
+ console.error("Error updating artist data:", error);
+ return NextResponse.json(
+ { success: false, message: "Failed to update artist profile" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/admin/settings/personal-message/route.ts b/src/app/api/admin/settings/personal-message/route.ts
new file mode 100644
index 0000000..5bd39ce
--- /dev/null
+++ b/src/app/api/admin/settings/personal-message/route.ts
@@ -0,0 +1,163 @@
+import { NextRequest, NextResponse } from "next/server";
+import { verifyAdminToken } from "@/lib/admin-auth";
+import { PersonalMessageUpdate } from "@/types/admin";
+import { promises as fs } from "fs";
+import { getTenantIdFromRequest, tenantDataPath } from "@/lib/tenant";
+
+export const runtime = "nodejs";
+
+// Helper function to verify admin authentication
+function getAdminSession(request: NextRequest) {
+ // Try to get token from cookie first, then from Authorization header
+ const cookieToken = request.cookies.get("admin-token")?.value;
+ const authHeader = request.headers.get("authorization");
+ const headerToken = authHeader?.startsWith("Bearer ")
+ ? authHeader.slice(7)
+ : null;
+
+ const token = cookieToken || headerToken;
+
+ console.log("Auth debug:", {
+ hasCookie: !!cookieToken,
+ hasHeader: !!headerToken,
+ hasToken: !!token,
+ cookieValue: cookieToken ? "present" : "missing",
+ });
+
+ if (!token) {
+ console.log("No token found");
+ return null;
+ }
+
+ try {
+ const result = verifyAdminToken(token);
+ console.log("Token verification result:", result ? "valid" : "invalid");
+ return result;
+ } catch (error) {
+ console.error("Token verification error:", error);
+ return null;
+ }
+}
+
+export async function GET(request: NextRequest) {
+ try {
+ // Verify admin authentication
+ const adminSession = getAdminSession(request);
+ if (!adminSession) {
+ return NextResponse.json(
+ { success: false, message: "Unauthorized" },
+ { status: 401 },
+ );
+ }
+
+ // Resolve tenant
+ const tenantId = await getTenantIdFromRequest(request);
+ if (!tenantId) {
+ return NextResponse.json(
+ { success: false, message: "Unknown tenant" },
+ { status: 404 },
+ );
+ }
+
+ // Read current personal message
+ const filePath = tenantDataPath(tenantId, "personal-message.json");
+ const fileContent = await fs.readFile(filePath, "utf-8");
+ const personalMessage = JSON.parse(fileContent);
+
+ return NextResponse.json({
+ success: true,
+ data: personalMessage,
+ });
+ } catch (error) {
+ console.error("Error reading personal message:", error);
+ return NextResponse.json(
+ { success: false, message: "Failed to read personal message" },
+ { status: 500 },
+ );
+ }
+}
+
+export async function PUT(request: NextRequest) {
+ try {
+ // Verify admin authentication
+ const adminSession = getAdminSession(request);
+ if (!adminSession) {
+ return NextResponse.json(
+ { success: false, message: "Unauthorized" },
+ { status: 401 },
+ );
+ }
+
+ // Resolve tenant
+ const tenantId = await getTenantIdFromRequest(request);
+ if (!tenantId) {
+ return NextResponse.json(
+ { success: false, message: "Unknown tenant" },
+ { status: 404 },
+ );
+ }
+
+ const body: PersonalMessageUpdate = await request.json();
+ const { enabled, recipient, message, dismissible } = body;
+
+ // Validate input
+ if (
+ typeof enabled !== "boolean" ||
+ typeof recipient !== "string" ||
+ typeof message !== "string" ||
+ typeof dismissible !== "boolean"
+ ) {
+ return NextResponse.json(
+ { success: false, message: "Invalid input data" },
+ { status: 400 },
+ );
+ }
+
+ // Validate string lengths
+ if (recipient.length > 100) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: "Recipient name too long (max 100 characters)",
+ },
+ { status: 400 },
+ );
+ }
+
+ if (message.length > 2000) {
+ return NextResponse.json(
+ { success: false, message: "Message too long (max 2000 characters)" },
+ { status: 400 },
+ );
+ }
+
+ // Create updated personal message object
+ const updatedMessage = {
+ enabled,
+ recipient: recipient.trim(),
+ message: message.trim(),
+ dismissible,
+ ariaLabel: `Personal message for ${recipient.trim()}`,
+ };
+
+ // Write to tenant-scoped file
+ const filePath = tenantDataPath(tenantId, "personal-message.json");
+ await fs.writeFile(
+ filePath,
+ JSON.stringify(updatedMessage, null, 2),
+ "utf-8",
+ );
+
+ return NextResponse.json({
+ success: true,
+ message: "Personal message updated successfully",
+ data: updatedMessage,
+ });
+ } catch (error) {
+ console.error("Error updating personal message:", error);
+ return NextResponse.json(
+ { success: false, message: "Failed to update personal message" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts
new file mode 100644
index 0000000..4cbb38e
--- /dev/null
+++ b/src/app/api/chat/route.ts
@@ -0,0 +1,137 @@
+import { promises as fs } from "fs";
+import { streamText, convertToModelMessages } from "ai";
+import { openrouter } from "@/lib/openrouter";
+import { getArtistData } from "@/lib/artist-data";
+import { mergeArtworksWithTranslations } from "@/lib/artworks";
+import { supportedLocaleCodes, localeToLanguageName, LOCALE_KEYS } from "@/lib/locales";
+import { clientToolDefs } from "@/lib/chat-tools";
+import { getTenantIdFromRequest, tenantMessagePath } from "@/lib/tenant";
+import {
+ ACCENT_COLOR_KEYS,
+ COLOR_SCHEME_KEYS,
+ parseClientTheme,
+} from "@/types/theme";
+
+/**
+ * The model believes the chat UI can only display this many characters.
+ * Lower = punchier replies. This is a soft hint, not a hard truncation.
+ */
+const CHAT_MAX_CHARS = 256;
+
+export async function POST(req: Request) {
+ if (!process.env.OPENROUTER_API_KEY) {
+ console.warn("[chat] OPENROUTER_API_KEY is not set — chat disabled");
+ return new Response(
+ JSON.stringify({ error: "Chat is not configured yet." }),
+ { status: 503, headers: { "Content-Type": "application/json" } },
+ );
+ }
+
+ // Resolve tenant from Host header
+ const tenantId = await getTenantIdFromRequest(req);
+ if (!tenantId) {
+ return new Response(JSON.stringify({ error: "Unknown tenant." }), {
+ status: 404,
+ headers: { "Content-Type": "application/json" },
+ });
+ }
+
+ try {
+ const { messages, locale: rawLocale = "en", currentTheme: rawTheme } = await req.json();
+
+ // Validate locale — fall back to English if unrecognised
+ const locale = supportedLocaleCodes.has(rawLocale) ? rawLocale : "en";
+
+ // Validate client-supplied theme — enum whitelist, safe defaults
+ const currentTheme = parseClientTheme(rawTheme);
+
+ // Load locale-specific artwork translations from tenant directory
+ const translationsRaw = await fs.readFile(
+ tenantMessagePath("artworks", tenantId, `${locale}.json`),
+ "utf-8",
+ );
+ const translationsData = JSON.parse(translationsRaw);
+
+ const t = (key: string): string => {
+ const parts = key.split(".");
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let value: any = translationsData;
+ for (const part of parts) {
+ value = value?.[part];
+ }
+ return typeof value === "string" ? value : key;
+ };
+
+ // Load artist data in the user's locale
+ const artist = await getArtistData(tenantId, locale);
+
+ // Build a compact catalog string for the system prompt
+ const sections = await mergeArtworksWithTranslations(tenantId, t);
+ const catalog = sections
+ .map(
+ (s) =>
+ `${s.title}: ` +
+ s.artworks
+ .map((a) => `"${a.title}" (${a.medium}, ${a.dimensions}, ${a.year})`)
+ .join("; "),
+ )
+ .join("\n");
+
+ const languageName = localeToLanguageName[locale] ?? "English";
+
+ const result = streamText({
+ model: openrouter("openai/gpt-4o-mini"),
+ system:
+ `You ARE ${artist.name}, the artist whose gallery the visitor is browsing. ` +
+ `Always speak in first person. When greeting a visitor or starting a conversation, ` +
+ `introduce yourself by name (${artist.name}) so they know who they're talking to.\n\n` +
+ `About you: ${artist.description}\n\n` +
+ `Your gallery catalog:\n${catalog}\n\n` +
+ "Use this catalog when visitors ask about your artworks. " +
+ "Share your passion for art, your creative process, techniques, " +
+ "and the stories behind your pieces. Keep responses concise and warm. " +
+ "If asked about something unrelated to art, gently steer the " +
+ "conversation back to your art.\n\n" +
+ `The gallery currently uses the "${currentTheme.accent}" accent color ` +
+ `and "${currentTheme.resolvedColorScheme}" appearance ` +
+ `(user preference: "${currentTheme.colorScheme}").\n\n` +
+ "You have a `setTheme` tool that can change the gallery's accent color " +
+ `(${ACCENT_COLOR_KEYS.join(", ")}) and color scheme (${COLOR_SCHEME_KEYS.join(", ")}). ` +
+ "Use it when the visitor asks to change the appearance. " +
+ "After calling the tool, confirm the change briefly. " +
+ "You also have a `getTheme` tool that returns the current theme state — " +
+ "call it to verify the theme after making changes.\n\n" +
+ "You have a `setLanguage` tool that can switch the gallery's display language " +
+ `(${LOCALE_KEYS.join(", ")}). ` +
+ "Use it when the visitor asks to change language. " +
+ "After calling the tool, confirm the change briefly. " +
+ "You also have a `getLanguage` tool that returns the current locale — " +
+ "call it to check before suggesting or confirming changes.\n\n" +
+ `The chat interface is very small and can only display ${CHAT_MAX_CHARS} characters per message. ` +
+ "Keep every reply within that limit — be concise and conversational.\n\n" +
+ `Always respond in the user's language (${languageName}).`,
+ messages: await convertToModelMessages(messages),
+ tools: clientToolDefs,
+ onError({ error }) {
+ console.error("[chat] Stream error:", error);
+ },
+ });
+
+ return result.toUIMessageStreamResponse();
+ } catch (error: unknown) {
+ console.error("[chat] API error:", error);
+
+ const message =
+ error instanceof Error ? error.message : "Something went wrong.";
+
+ // Strip internal details — only surface safe, generic info
+ const safeMessage = message.includes("authenticate")
+ ? "Unable to reach the AI service. Please try again later."
+ : "Something went wrong. Please try again.";
+
+ return new Response(JSON.stringify({ error: safeMessage }), {
+ status: 502,
+ headers: { "Content-Type": "application/json" },
+ });
+ }
+}
diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts
new file mode 100644
index 0000000..a6c0ca0
--- /dev/null
+++ b/src/app/api/health/route.ts
@@ -0,0 +1,8 @@
+import { NextResponse } from "next/server";
+
+export async function GET() {
+ return NextResponse.json({
+ status: "ok",
+ timestamp: new Date().toISOString(),
+ });
+}
diff --git a/src/app/api/og/route.tsx b/src/app/api/og/route.tsx
new file mode 100644
index 0000000..dc5b5d2
--- /dev/null
+++ b/src/app/api/og/route.tsx
@@ -0,0 +1,284 @@
+import { ImageResponse } from "next/og";
+import { getTranslations } from "next-intl/server";
+
+export async function GET(request: Request) {
+ try {
+ // Get parameters from query params
+ const { searchParams } = new URL(request.url);
+ const locale = searchParams.get("locale") || "en";
+ const artworkId = searchParams.get("artworkId");
+ const artworkTitle = searchParams.get("artworkTitle");
+ const artworkDescription = searchParams.get("artworkDescription");
+ const artworkCategory = searchParams.get("artworkCategory");
+
+ // Get translations for the specified locale
+ const t = await getTranslations({ locale });
+
+ const siteName = t("Site.name", { artistName: t("Artist.name") });
+ const siteDescription = t("Site.description");
+
+ // Determine if this is an artwork-specific OG image
+ const isArtworkPage = artworkId && artworkTitle;
+
+ // Load fonts for international character support (edge runtime compatible)
+ // Fail hard if base URL or fonts are missing - force developers to fix configuration
+ if (!process.env.NEXT_PUBLIC_BASE_URL) {
+ throw new Error(
+ "NEXT_PUBLIC_BASE_URL environment variable is required for OpenGraph image generation",
+ );
+ }
+
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;
+
+ const [
+ regularResponse,
+ boldResponse,
+ georgianResponse,
+ georgianBoldResponse,
+ ] = await Promise.all([
+ fetch(new URL("/fonts/Noto_Sans/static/NotoSans-Regular.ttf", baseUrl)),
+ fetch(new URL("/fonts/Noto_Sans/static/NotoSans-Bold.ttf", baseUrl)),
+ fetch(
+ new URL(
+ "/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Regular.ttf",
+ baseUrl,
+ ),
+ ),
+ fetch(
+ new URL(
+ "/fonts/Noto_Sans_Georgian/static/NotoSansGeorgian-Bold.ttf",
+ baseUrl,
+ ),
+ ),
+ ]);
+
+ // Fail hard if any font is missing
+ if (!regularResponse.ok) {
+ throw new Error(
+ `Failed to load Noto Sans Regular font: ${regularResponse.status} ${regularResponse.statusText}`,
+ );
+ }
+ if (!boldResponse.ok) {
+ throw new Error(
+ `Failed to load Noto Sans Bold font: ${boldResponse.status} ${boldResponse.statusText}`,
+ );
+ }
+ if (!georgianResponse.ok) {
+ throw new Error(
+ `Failed to load Noto Sans Georgian Regular font: ${georgianResponse.status} ${georgianResponse.statusText}`,
+ );
+ }
+ if (!georgianBoldResponse.ok) {
+ throw new Error(
+ `Failed to load Noto Sans Georgian Bold font: ${georgianBoldResponse.status} ${georgianBoldResponse.statusText}`,
+ );
+ }
+
+ const [regularFont, boldFont, georgianFont, georgianBoldFont] =
+ await Promise.all([
+ regularResponse.arrayBuffer(),
+ boldResponse.arrayBuffer(),
+ georgianResponse.arrayBuffer(),
+ georgianBoldResponse.arrayBuffer(),
+ ]);
+
+ return new ImageResponse(
+
+ {/* Decorative circles */}
+
+
+
+
+ {isArtworkPage ? (
+
+ {/* Artwork-specific content */}
+
+ {siteName}
+
+
+ {artworkTitle}
+
+ {artworkDescription && (
+
+ {artworkDescription.length > 80
+ ? artworkDescription.substring(0, 80) + "..."
+ : artworkDescription}
+
+ )}
+ {artworkCategory && (
+
+ {artworkCategory}
+
+ )}
+
+ ) : (
+ <>
+ {/* General site content */}
+
+ {siteName}
+
+
+ {siteDescription}
+
+ >
+ )}
+
+
,
+ {
+ width: 1200,
+ height: 630,
+ fonts: [
+ {
+ name: "Noto Sans",
+ data: regularFont,
+ weight: 400,
+ style: "normal",
+ },
+ {
+ name: "Noto Sans",
+ data: boldFont,
+ weight: 700,
+ style: "normal",
+ },
+ {
+ name: "Noto Sans Georgian",
+ data: georgianFont,
+ weight: 400,
+ style: "normal",
+ },
+ {
+ name: "Noto Sans Georgian",
+ data: georgianBoldFont,
+ weight: 700,
+ style: "normal",
+ },
+ ],
+ },
+ );
+ } catch (e: unknown) {
+ const error = e as Error;
+ console.log(`${error.message}`);
+ return new Response(`Failed to generate the image`, {
+ status: 500,
+ });
+ }
+}
diff --git a/src/app/api/transcribe/route.ts b/src/app/api/transcribe/route.ts
new file mode 100644
index 0000000..268e72e
--- /dev/null
+++ b/src/app/api/transcribe/route.ts
@@ -0,0 +1,113 @@
+import { generateText } from "ai";
+import { openrouter } from "@/lib/openrouter";
+import { supportedLocaleCodes, localeToLanguageName } from "@/lib/locales";
+
+/**
+ * Dedicated audio-to-text transcription endpoint.
+ *
+ * Uses `openai/gpt-audio` on OpenRouter — a model purpose-built for audio.
+ * The chat route keeps using `openai/gpt-4o-mini` for text, so neither
+ * model's quality is compromised.
+ */
+
+const TRANSCRIPTION_MODEL = "openai/gpt-audio";
+
+const ALLOWED_MIME_TYPES = new Set([
+ "audio/wav",
+ "audio/wave",
+ "audio/x-wav",
+ "audio/mpeg",
+ "audio/mp3",
+ "audio/mp4",
+ "audio/webm",
+ "audio/ogg",
+]);
+
+export async function POST(req: Request) {
+ if (!process.env.OPENROUTER_API_KEY) {
+ return Response.json(
+ { error: "Transcription is not configured." },
+ { status: 503 },
+ );
+ }
+
+ try {
+ const body = await req.json();
+ const { audio, mimeType, locale: rawLocale } = body as {
+ audio?: string;
+ mimeType?: string;
+ locale?: string;
+ };
+
+ // ── Validation ────────────────────────────────────────────────
+ if (!audio || typeof audio !== "string" || audio.length === 0) {
+ return Response.json(
+ { error: "Missing or empty audio data." },
+ { status: 400 },
+ );
+ }
+
+ if (!mimeType || !ALLOWED_MIME_TYPES.has(mimeType)) {
+ return Response.json(
+ { error: `Unsupported audio format: ${mimeType}` },
+ { status: 400 },
+ );
+ }
+
+ const locale =
+ typeof rawLocale === "string" && supportedLocaleCodes.has(rawLocale)
+ ? rawLocale
+ : "en";
+
+ const languageName = localeToLanguageName[locale] ?? "English";
+
+ // ── Transcribe via OpenRouter audio model ─────────────────────
+ const result = await generateText({
+ model: openrouter(TRANSCRIPTION_MODEL),
+ messages: [
+ {
+ role: "system",
+ content:
+ `Transcribe the following audio exactly in ${languageName}. ` +
+ "Return ONLY the transcription text — no preamble, no explanation, no formatting. " +
+ "If the audio contains only silence, background noise, or no discernible speech, " +
+ "return exactly the string [SILENCE] and nothing else.",
+ },
+ {
+ role: "user",
+ content: [
+ {
+ type: "file",
+ data: audio, // raw base64
+ mediaType: mimeType,
+ },
+ {
+ type: "text",
+ text: "Transcribe this audio.",
+ },
+ ],
+ },
+ ],
+ });
+
+ const raw = result.text?.trim() ?? "";
+
+ // The prompt asks the model to return "[SILENCE]" for empty audio.
+ // Treat it (and common close variants) as no speech detected.
+ const text = /^\[?\s*SILENCE\s*\]?$/i.test(raw) ? "" : raw;
+
+ return Response.json({ text });
+ } catch (error: unknown) {
+ console.error("[transcribe] Error:", error);
+
+ const message =
+ error instanceof Error ? error.message : "Transcription failed.";
+
+ return Response.json(
+ { error: message.includes("authenticate")
+ ? "Unable to reach the transcription service."
+ : "Transcription failed. Please try again." },
+ { status: 502 },
+ );
+ }
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index 6b717ad..800ca82 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,10 +1,26 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@import "tailwindcss";
+
+@custom-variant dark (&:where(.dark, .dark *));
+
+@theme {
+ /* Accent color system - dynamically updated by ThemeProvider */
+ --color-accent-50: var(--accent-50, oklch(0.971 0.014 343.198));
+ --color-accent-100: var(--accent-100, oklch(0.948 0.028 342.258));
+ --color-accent-200: var(--accent-200, oklch(0.899 0.061 343.231));
+ --color-accent-500: var(--accent-500, oklch(0.656 0.241 354.308));
+ --color-accent-600: var(--accent-600, oklch(0.592 0.249 0.584));
+}
:root {
--background: #ffffff;
--foreground: #171717;
+
+ /* Default accent colors (pink) - will be overridden by ThemeProvider */
+ --accent-50: oklch(0.971 0.014 343.198);
+ --accent-100: oklch(0.948 0.028 342.258);
+ --accent-200: oklch(0.899 0.061 343.231);
+ --accent-500: oklch(0.656 0.241 354.308);
+ --accent-600: oklch(0.592 0.249 0.584);
}
@media (prefers-color-scheme: dark) {
@@ -19,3 +35,42 @@ body {
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
+
+/* Animation for theme selector */
+@keyframes slide-in-from-bottom {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.animate-in {
+ animation-fill-mode: both;
+}
+
+.slide-in-from-bottom-2 {
+ animation-name: slide-in-from-bottom;
+}
+
+.duration-200 {
+ animation-duration: 200ms;
+}
+
+/* Smooth radial wave animation for theme button */
+@keyframes radialWave {
+ 0% {
+ transform: scale(1);
+ opacity: 0.4;
+ }
+ 50% {
+ opacity: 0.6;
+ }
+ 100% {
+ transform: scale(2);
+ opacity: 0;
+ }
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..6ecd049 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,34 +1,21 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Inter } from "next/font/google";
import "./globals.css";
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
+const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ // No metadataBase needed since we removed OpenGraph images
};
export default function RootLayout({
children,
-}: Readonly<{
+}: {
children: React.ReactNode;
-}>) {
+}) {
return (
-
-
- {children}
-
+
+ {children}
);
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 3eee014..02b1f56 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,101 +1,8 @@
-import Image from "next/image";
+import { redirect } from "next/navigation";
-export default function Home() {
- return (
-
-
-
-
-
- Get started by editing{" "}
-
- src/app/page.tsx
-
- .
-
- Save and see your changes instantly.
-
-
-
-
-
-
- );
+// Minimal fallback — the next-intl middleware handles root redirect with
+// accept-language negotiation and cookie detection. This page should
+// never be reached in normal operation.
+export default function RootPage() {
+ redirect("/en");
}
diff --git a/src/components/ArtworkCard.tsx b/src/components/ArtworkCard.tsx
new file mode 100644
index 0000000..b9fa78d
--- /dev/null
+++ b/src/components/ArtworkCard.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import { Artwork } from "@/types/artwork";
+import Link from "next/link";
+import { useTranslations, useLocale } from "next-intl";
+
+interface ArtworkCardProps {
+ artwork: Artwork;
+}
+
+export default function ArtworkCard({ artwork }: ArtworkCardProps) {
+ const locale = useLocale();
+ const t = useTranslations("Artwork");
+
+ return (
+
+
+
+
+
+ {artwork.title}
+
+
+ {artwork.description}
+
+
+ {artwork.medium && (
+ <>
+ {t("medium")}
+ {artwork.medium}
+ >
+ )}
+ {artwork.dimensions && (
+ <>
+ {t("dimensions")}
+ {artwork.dimensions}
+ >
+ )}
+ {artwork.year && (
+ <>
+ {t("year")}
+ {artwork.year}
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/src/components/ArtworkDetailView.tsx b/src/components/ArtworkDetailView.tsx
new file mode 100644
index 0000000..ace6843
--- /dev/null
+++ b/src/components/ArtworkDetailView.tsx
@@ -0,0 +1,150 @@
+"use client";
+
+import { Artwork } from "@/types/artwork";
+import { ArrowLeftIcon } from "@heroicons/react/24/outline";
+import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
+
+interface ArtworkDetailViewProps {
+ artwork: Artwork;
+}
+
+export default function ArtworkDetailView({ artwork }: ArtworkDetailViewProps) {
+ const router = useRouter();
+ const t = useTranslations();
+
+ const handleBack = () => {
+ router.back();
+ };
+
+ return (
+
+
+
+
+
+ {t("ArtworkDetail.backToGallery")}
+
+
+
+
+ {/* Main content */}
+
+
+ {/* Artwork image */}
+
+
+
+ {/* Image caption */}
+
+
+ {t("ArtworkDetail.categoryArtwork", {
+ category: t(`Categories.${artwork.category}.title`),
+ })}
+
+
+
+
+ {/* Artwork details */}
+
+
+
+ {artwork.title}
+
+
+ {artwork.description}
+
+
+
+ {/* Artwork metadata */}
+
+
+ {t("ArtworkDetail.artworkDetails")}
+
+
+
+
+
+ {t("ArtworkDetail.category")}
+
+
+ {t(`Categories.${artwork.category}.title`)}
+
+
+
+ {artwork.medium && (
+
+
+ {t("Artwork.medium")}
+
+
+ {artwork.medium}
+
+
+ )}
+
+ {artwork.dimensions && (
+
+
+ {t("Artwork.dimensions")}
+
+
+ {artwork.dimensions}
+
+
+ )}
+
+ {artwork.year && (
+
+
+ {t("Artwork.year")}
+
+
+ {artwork.year}
+
+
+ )}
+
+
+
+ {/* Additional actions */}
+
+
+ {t("ArtworkDetail.viewMoreArtworks")}
+
+
+ {
+ if (navigator.share) {
+ navigator.share({
+ title: artwork.title,
+ text: artwork.description,
+ url: window.location.href,
+ });
+ } else {
+ navigator.clipboard.writeText(window.location.href);
+ }
+ }}
+ className="flex-1 bg-accent-600 dark:bg-accent-500 text-white px-6 py-3 rounded-lg font-medium hover:bg-accent-700 dark:hover:bg-accent-600 transition-colors"
+ >
+ {t("ArtworkDetail.shareArtwork")}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ArtworkGrid.tsx b/src/components/ArtworkGrid.tsx
new file mode 100644
index 0000000..44a4687
--- /dev/null
+++ b/src/components/ArtworkGrid.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { Artwork } from "@/types/artwork";
+import ArtworkCard from "./ArtworkCard";
+import { useTranslations } from "next-intl";
+
+interface ArtworkGridProps {
+ artworks: Artwork[];
+ category: string;
+}
+
+export default function ArtworkGrid({ artworks, category }: ArtworkGridProps) {
+ const t = useTranslations();
+
+ return (
+
+ {artworks.map((artwork) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/ChatToggle.tsx b/src/components/ChatToggle.tsx
new file mode 100644
index 0000000..b5e2fce
--- /dev/null
+++ b/src/components/ChatToggle.tsx
@@ -0,0 +1,159 @@
+"use client";
+
+import { useState, useEffect, useRef, useMemo, useCallback } from "react";
+import { useTranslations, useLocale } from "next-intl";
+import { ChatBubbleLeftRightIcon } from "@heroicons/react/24/outline";
+import ChatWidget, { CHAT_STORAGE_KEY } from "./ChatWidget";
+import { useRouter, usePathname } from "@/i18n/navigation";
+import { useTheme } from "./theme/ThemeProvider";
+import type { AccentColor, ColorScheme } from "@/types/theme";
+import type { ChatToolHandler } from "@/lib/chat-tools";
+import { supportedLocaleCodes, getLocaleConfig } from "@/lib/locales";
+
+/**
+ * Layout-level chat bubble button + panel.
+ * Sits next to the ThemeSelector in the fixed bottom-right corner.
+ * Owns the open/close state; delegates everything else to ChatWidget.
+ *
+ * Also bridges client-side chat tools (e.g. `setTheme`) to app state
+ * so the AI can change the gallery's appearance on request.
+ */
+const CHAT_OPEN_KEY = "chat-open";
+
+export default function ChatToggle() {
+ const [isOpen, setIsOpen] = useState(() => {
+ if (typeof window === "undefined") return false;
+ try { return sessionStorage.getItem(CHAT_OPEN_KEY) === "1"; } catch { return false; }
+ });
+ const [chatSessionKey, setChatSessionKey] = useState(0);
+
+ // Persist open/closed state so the panel survives locale switches.
+ useEffect(() => {
+ try {
+ if (isOpen) sessionStorage.setItem(CHAT_OPEN_KEY, "1");
+ else sessionStorage.removeItem(CHAT_OPEN_KEY);
+ } catch { /* private browsing / quota */ }
+ }, [isOpen]);
+
+ const t = useTranslations();
+ const locale = useLocale();
+ const router = useRouter();
+ const pathname = usePathname();
+ const { theme, setAccentColor, setColorScheme, resolvedColorScheme } =
+ useTheme();
+
+ // ── Deferred locale navigation ──────────────────────────────────
+ // The setLanguage tool handler stashes the target here; the actual
+ // router.replace fires only after the model finishes streaming its
+ // confirmation (via the onReady callback from ChatWidget).
+ const pendingLocaleRef = useRef(null);
+
+ const handleReady = useCallback(() => {
+ const target = pendingLocaleRef.current;
+ if (!target) return;
+ pendingLocaleRef.current = null;
+ router.replace(pathname, { locale: target });
+ }, [router, pathname]);
+
+ /** Current theme state sent to the server on every request. */
+ const extraBody = useMemo(
+ () => ({
+ currentTheme: {
+ accent: theme.accent,
+ colorScheme: theme.colorScheme,
+ resolvedColorScheme,
+ },
+ }),
+ [theme.accent, theme.colorScheme, resolvedColorScheme],
+ );
+
+ /** Client-side tool handlers passed to ChatWidget. */
+ const clientTools = useMemo>(
+ () => ({
+ setTheme: (input) => {
+ const { accent, colorScheme } = input as {
+ accent?: AccentColor;
+ colorScheme?: ColorScheme;
+ };
+
+ if (accent) setAccentColor(accent);
+ if (colorScheme) setColorScheme(colorScheme);
+
+ const changes: string[] = [];
+ if (accent) changes.push(`accent → ${accent}`);
+ if (colorScheme) changes.push(`color scheme → ${colorScheme}`);
+
+ return changes.length > 0
+ ? `Theme updated: ${changes.join(", ")}.`
+ : "No changes — neither accent nor colorScheme was provided.";
+ },
+ getTheme: () => {
+ return JSON.stringify({
+ accent: theme.accent,
+ colorScheme: theme.colorScheme,
+ resolvedColorScheme,
+ });
+ },
+ setLanguage: (input) => {
+ const { locale: target } = input as { locale?: string };
+ if (!target || !supportedLocaleCodes.has(target)) {
+ const supported = [...supportedLocaleCodes].join(", ");
+ return `Unsupported locale "${target}". Supported: ${supported}.`;
+ }
+ if (target === locale) {
+ const config = getLocaleConfig(target);
+ return `Already using ${config?.name ?? target} (${target}).`;
+ }
+ // Stash the target — actual navigation is deferred until the model
+ // finishes streaming its confirmation (via onReady).
+ pendingLocaleRef.current = target;
+ const config = getLocaleConfig(target);
+ return `Language switched to ${config?.name ?? target} (${target}).`;
+ },
+ getLanguage: () => {
+ const config = getLocaleConfig(locale);
+ return JSON.stringify({
+ locale,
+ name: config?.name ?? locale,
+ });
+ },
+ }),
+ [setAccentColor, setColorScheme, theme.accent, theme.colorScheme, resolvedColorScheme, locale],
+ );
+
+ return (
+
+ {/* Bubble button — visible when panel is closed */}
+ {!isOpen && (
+
setIsOpen(true)}
+ className="w-12 h-12 bg-accent-500 shadow-lg rounded-full border border-accent-600 hover:bg-accent-600 hover:shadow-xl transition-all duration-200 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-500 flex items-center justify-center"
+ aria-label={t("Chat.openChat")}
+ >
+
+
+ )}
+
+ {/* Chat panel — keep mounted so close does not reset context */}
+
+ setIsOpen(false)}
+ onReset={() => {
+ try { sessionStorage.removeItem(CHAT_STORAGE_KEY); } catch { /* private browsing */ }
+ setChatSessionKey((prev) => prev + 1);
+ }}
+ clientTools={clientTools}
+ extraBody={extraBody}
+ onReady={handleReady}
+ />
+
+
+ );
+}
diff --git a/src/components/ChatWidget.tsx b/src/components/ChatWidget.tsx
new file mode 100644
index 0000000..f5096bc
--- /dev/null
+++ b/src/components/ChatWidget.tsx
@@ -0,0 +1,828 @@
+"use client";
+
+import { useState, useRef, useEffect, useCallback, useMemo } from "react";
+import { useChat, type UIMessage } from "@ai-sdk/react";
+import {
+ DefaultChatTransport,
+ lastAssistantMessageIsCompleteWithToolCalls,
+} from "ai";
+import { useTranslations, useLocale } from "next-intl";
+import {
+ XMarkIcon,
+ PaperAirplaneIcon,
+ ArrowPathIcon,
+ TrashIcon,
+ MicrophoneIcon,
+ StopIcon,
+ Cog6ToothIcon,
+} from "@heroicons/react/24/outline";
+import type { ChatToolHandler } from "@/lib/chat-tools";
+import { blobToWav, blobToBase64, isWavSilent } from "@/lib/wav-encoder";
+import { toSpeechLang } from "@/lib/locales";
+import type {
+ SpeechRecognitionEvent,
+ SpeechRecognitionErrorEvent,
+ SpeechRecognitionInstance,
+} from "@/lib/speech-recognition";
+import { getSpeechRecognition } from "@/lib/speech-recognition";
+
+// ── Chat message persistence ──────────────────────────────────────
+// Key is intentionally locale-independent so the conversation survives
+// language switches (which re-mount the entire [locale] layout tree).
+export const CHAT_STORAGE_KEY = "chat-messages";
+
+/** Read previously stored messages from sessionStorage (runs once on mount). */
+function loadStoredMessages(): UIMessage[] | undefined {
+ if (typeof window === "undefined") return undefined;
+ try {
+ const raw = sessionStorage.getItem(CHAT_STORAGE_KEY);
+ if (!raw) return undefined;
+ const parsed: UIMessage[] = JSON.parse(raw);
+ return parsed.length > 0 ? parsed : undefined;
+ } catch {
+ return undefined;
+ }
+}
+
+// ── Dictation mode types ──────────────────────────────────────────
+type DictationMode = "browser" | "server";
+const DICTATION_STORAGE_KEY = "chat-dictation-mode";
+
+/** Format seconds as m:ss for the recording timer. */
+function formatRecordingTime(seconds: number): string {
+ const m = Math.floor(seconds / 60);
+ const s = seconds % 60;
+ return `${m}:${s.toString().padStart(2, "0")}`;
+}
+
+/**
+ * Reusable chat panel — "I am the window", not the button that opens me.
+ *
+ * The parent controls visibility (`open` / `onClose`).
+ * The parent also controls positioning (e.g. fixed bottom-right, sidebar,
+ * modal — whatever the layout needs). This component just fills whatever
+ * container it's placed in.
+ *
+ * Wrap it in a positioned container to get floating-window behaviour:
+ *
+ * ```tsx
+ * {isOpen && (
+ *
+ * setIsOpen(false)} />
+ *
+ * )}
+ * ```
+ */
+interface ChatWidgetProps {
+ /** Whether the panel is visible. */
+ open: boolean;
+ /** Called when the user clicks the close button inside the panel. */
+ onClose: () => void;
+ /** Override the API endpoint (defaults to "/api/chat") */
+ apiEndpoint?: string;
+ /**
+ * Client-side tool handlers keyed by tool name.
+ * Each handler receives the tool input and returns a short result string
+ * that the model will see as the tool output.
+ *
+ * Tools are declared on the server (no `execute`), so the model can call
+ * them; the handlers here run on the client when the call arrives.
+ */
+ clientTools?: Record;
+ /** Extra key-value pairs merged into every request body (e.g. theme state). */
+ extraBody?: Record;
+ /** Explicit user-initiated reset action from the header bin button. */
+ onReset?: () => void;
+ /**
+ * Fires once when the chat stream finishes and status returns to "ready".
+ * Useful for deferring side-effects (e.g. navigation) until the model's
+ * response has been fully streamed and persisted.
+ */
+ onReady?: () => void;
+}
+
+export default function ChatWidget({
+ open,
+ onClose,
+ apiEndpoint = "/api/chat",
+ clientTools,
+ extraBody,
+ onReset,
+ onReady,
+}: ChatWidgetProps) {
+ const t = useTranslations();
+ const locale = useLocale();
+ const speechLang = useMemo(() => toSpeechLang(locale), [locale]);
+ const [input, setInput] = useState("");
+ const messagesEndRef = useRef(null);
+ const inputRef = useRef(null);
+
+ // ── Dictation mode (browser vs server) ──────────────────────────
+ const [dictationMode, setDictationMode] = useState(() => {
+ if (typeof window === "undefined") return "server";
+ try {
+ const stored = localStorage.getItem(DICTATION_STORAGE_KEY);
+ if (stored === "browser" || stored === "server") return stored;
+ } catch { /* private browsing / quota */ }
+ return "server";
+ });
+ const [settingsOpen, setSettingsOpen] = useState(false);
+
+ // Persist dictation mode preference
+ useEffect(() => {
+ try { localStorage.setItem(DICTATION_STORAGE_KEY, dictationMode); } catch { /* private browsing / quota */ }
+ }, [dictationMode]);
+
+ // ── Speech-to-text dictation (browser mode) ─────────────────────
+ const [isListening, setIsListening] = useState(false);
+ const recognitionRef = useRef(null);
+ // Tracks whether we *want* to be listening. Chrome's SpeechRecognition
+ // can fire `onend` at any time (silence, network blip, etc.) even with
+ // `continuous: true`. When this ref is true, the `onend` handler will
+ // automatically restart the engine.
+ const shouldListenRef = useRef(false);
+ // Check once whether the browser supports the Web Speech API.
+ const [speechSupported, setSpeechSupported] = useState(false);
+ const [speechTemporarilyUnavailable, setSpeechTemporarilyUnavailable] =
+ useState(false);
+ useEffect(() => {
+ const supported = getSpeechRecognition() !== null;
+ setSpeechSupported(supported);
+ // If "browser" was stored but this browser doesn't support it, auto-correct
+ if (!supported) {
+ setDictationMode((prev) => (prev === "browser" ? "server" : prev));
+ }
+ }, []);
+
+ // ── Server-side recording state ─────────────────────────────────
+ const [isRecording, setIsRecording] = useState(false);
+ const [isTranscribing, setIsTranscribing] = useState(false);
+ const [recordingSeconds, setRecordingSeconds] = useState(0);
+ const mediaRecorderRef = useRef(null);
+ const audioChunksRef = useRef([]);
+ const recordingTimerRef = useRef | null>(
+ null,
+ );
+ const streamRef = useRef(null);
+ const transcribeAbortRef = useRef(null);
+
+ // ── Sound effects ────────────────────────────────────────────────
+ const sentSoundRef = useRef(null);
+ const receivedSoundRef = useRef(null);
+
+ // Preload audio files once on mount (safe in "use client" components).
+ useEffect(() => {
+ sentSoundRef.current = new Audio("/sounds/message_sent.mp3");
+ receivedSoundRef.current = new Audio("/sounds/new_notification.mp3");
+ }, []);
+
+ const playSent = useCallback(() => {
+ const s = sentSoundRef.current;
+ if (!s) return;
+ s.currentTime = 0;
+ s.play().catch(() => {});
+ }, []);
+
+ const playReceived = useCallback(() => {
+ const s = receivedSoundRef.current;
+ if (!s) return;
+ s.currentTime = 0;
+ s.play().catch(() => {});
+ }, []);
+
+ // ── Restore persisted conversation (computed once on mount) ──────
+ const [storedMessages] = useState(loadStoredMessages);
+
+ // Keep a ref so the onToolCall closure always sees the latest handlers
+ // without needing to re-create the useChat config on every render.
+ const clientToolsRef = useRef(clientTools);
+ clientToolsRef.current = clientTools;
+
+ // Serialise extraBody so useMemo only re-creates the transport when the
+ // values actually change (object identity would change every render).
+ const extraBodyKey = JSON.stringify(extraBody ?? {});
+
+ const transport = useMemo(
+ () =>
+ new DefaultChatTransport({
+ api: apiEndpoint,
+ body: { locale, ...extraBody },
+ }),
+ [apiEndpoint, locale, extraBodyKey], // extraBodyKey is the stable proxy for extraBody
+ );
+
+ const { messages, sendMessage, regenerate, addToolOutput, status, error, clearError } = useChat({
+ messages: storedMessages,
+ transport,
+
+ // After all client-side tool results are available, automatically send
+ // them back so the model can generate a follow-up text response.
+ sendAutomaticallyWhen: clientTools
+ ? lastAssistantMessageIsCompleteWithToolCalls
+ : undefined,
+
+ async onToolCall({ toolCall }) {
+ const handler = clientToolsRef.current?.[toolCall.toolName];
+ if (!handler) return;
+
+ try {
+ const output = await handler(
+ toolCall.input as Record,
+ );
+ addToolOutput({
+ tool: toolCall.toolName,
+ toolCallId: toolCall.toolCallId,
+ output,
+ });
+ } catch {
+ addToolOutput({
+ tool: toolCall.toolName,
+ toolCallId: toolCall.toolCallId,
+ state: "output-error",
+ errorText: "Tool execution failed.",
+ });
+ }
+ },
+
+ // Play the notification sound when the assistant finishes a text response.
+ // Skip aborts, disconnects, errors, and intermediate tool-call turns
+ // (those will be resubmitted automatically by sendAutomaticallyWhen).
+ onFinish({ isAbort, isDisconnect, isError, finishReason }) {
+ if (isAbort || isDisconnect || isError) return;
+ if (finishReason === "tool-calls") return;
+ playReceived();
+ },
+
+ onError() {
+ // handled via the `error` return value — no need to rethrow
+ },
+ });
+
+ const isLoading = status === "streaming" || status === "submitted";
+
+ // ── Persist messages to sessionStorage on every change ──────────
+ // Declared BEFORE the status watcher so messages are saved before
+ // onReady can trigger navigation (React runs effects in order).
+ useEffect(() => {
+ try {
+ if (messages.length > 0) {
+ sessionStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(messages));
+ } else {
+ sessionStorage.removeItem(CHAT_STORAGE_KEY);
+ }
+ } catch { /* private browsing / quota */ }
+ }, [messages]);
+
+ // ── Notify parent when a stream *truly* completes (active → ready) ─
+ // Guard: skip the intermediate "ready" after a tool-call message —
+ // sendAutomaticallyWhen is about to fire another round-trip. Only
+ // signal when the final text response is done.
+ const prevStatusRef = useRef(status);
+ useEffect(() => {
+ const wasActive =
+ prevStatusRef.current === "streaming" ||
+ prevStatusRef.current === "submitted";
+ prevStatusRef.current = status;
+ if (!wasActive || status !== "ready") return;
+ // If the last assistant message is a complete tool call, the auto-send
+ // will resubmit momentarily — don't fire onReady yet.
+ if (clientTools && lastAssistantMessageIsCompleteWithToolCalls({ messages })) return;
+ onReady?.();
+ }, [status, messages, clientTools, onReady]);
+
+ // Auto-scroll to bottom when new messages arrive
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, [messages]);
+
+ // Focus input when panel opens
+ useEffect(() => {
+ if (open) {
+ const timer = setTimeout(() => inputRef.current?.focus(), 150);
+ return () => clearTimeout(timer);
+ }
+ }, [open]);
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ const trimmed = input.trim();
+ if (!trimmed || isLoading) return;
+ sendMessage({ text: trimmed });
+ playSent();
+ setInput("");
+
+ // Reset textarea height after sending
+ if (inputRef.current) {
+ inputRef.current.style.height = "auto";
+ }
+ };
+
+ // Auto-resize textarea to fit content (up to max-h cap)
+ const autoResize = useCallback(() => {
+ const el = inputRef.current;
+ if (!el) return;
+ el.style.height = "auto";
+ el.style.height = `${el.scrollHeight}px`;
+ }, []);
+
+ // Enter sends, Shift+Enter inserts a newline
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ handleSubmit(e);
+ }
+ };
+
+ // ── Speech-to-text helpers ─────────────────────────────────────
+
+ // Mutable ref that holds the latest "committed" (final) transcript
+ // across recognition restarts so we don't lose words.
+ const committedRef = useRef("");
+
+ /** Stop the current recognition session intentionally. */
+ const stopListening = useCallback(() => {
+ shouldListenRef.current = false;
+ recognitionRef.current?.abort();
+ recognitionRef.current = null;
+ setIsListening(false);
+ }, []);
+
+ /** Tear down any in-progress server recording / transcription. */
+ const cleanupRecording = useCallback(() => {
+ transcribeAbortRef.current?.abort();
+ transcribeAbortRef.current = null;
+ if (recordingTimerRef.current) {
+ clearInterval(recordingTimerRef.current);
+ recordingTimerRef.current = null;
+ }
+ if (mediaRecorderRef.current?.state === "recording") {
+ try { mediaRecorderRef.current.stop(); } catch { /* already stopped */ }
+ }
+ streamRef.current?.getTracks().forEach((t) => t.stop());
+ streamRef.current = null;
+ mediaRecorderRef.current = null;
+ audioChunksRef.current = [];
+ setIsRecording(false);
+ setIsTranscribing(false);
+ setRecordingSeconds(0);
+ }, []);
+
+ // Ensure all dictation is stopped when the widget closes or unmounts.
+ useEffect(() => {
+ if (!open) {
+ stopListening();
+ cleanupRecording();
+ }
+ return () => {
+ stopListening();
+ cleanupRecording();
+ };
+ }, [open, stopListening, cleanupRecording]);
+
+ /**
+ * Create, configure, and start a SpeechRecognition instance.
+ * Extracted so both `startListening` and the auto-restart in
+ * `onend` can share the same setup logic.
+ */
+ const bootRecognition = useCallback(() => {
+ const SR = getSpeechRecognition();
+ if (!SR) return;
+
+ const recognition = new SR();
+ recognition.lang = speechLang;
+ recognition.interimResults = true;
+ recognition.continuous = true;
+
+ recognition.onresult = (event: SpeechRecognitionEvent) => {
+ let interim = "";
+ for (let i = event.resultIndex; i < event.results.length; i++) {
+ const transcript = event.results[i][0].transcript;
+ if (event.results[i].isFinal) {
+ const trimmed = transcript.trim();
+ if (trimmed) {
+ committedRef.current +=
+ (committedRef.current ? " " : "") + trimmed;
+ }
+ } else {
+ interim += transcript;
+ }
+ }
+
+ // Show committed + interim preview in the textarea
+ const preview = interim
+ ? committedRef.current +
+ (committedRef.current ? " " : "") +
+ interim
+ : committedRef.current;
+
+ setInput(preview);
+ // Auto-grow the textarea to fit the dictated text
+ requestAnimationFrame(() => {
+ const el = inputRef.current;
+ if (!el) return;
+ el.style.height = "auto";
+ el.style.height = `${el.scrollHeight}px`;
+ });
+ };
+
+ recognition.onerror = (event: SpeechRecognitionErrorEvent) => {
+ // Helpful during integration/testing: shows why no text is emitted.
+ console.warn("[chat] SpeechRecognition error:", event.error);
+ // Fatal errors that mean we should truly stop.
+ const fatal = new Set([
+ "not-allowed",
+ "service-not-allowed",
+ "language-not-supported",
+ "network",
+ ]);
+ if (fatal.has(event.error)) {
+ shouldListenRef.current = false;
+ if (event.error === "network") {
+ // Constructor exists but backend speech service is unreachable.
+ // Disable dictation button for this page session to avoid a retry loop.
+ setSpeechTemporarilyUnavailable(true);
+ }
+ }
+ // Non-fatal errors (no-speech, aborted, audio-capture)
+ // will trigger `onend`, which will auto-restart if shouldListenRef
+ // is still true.
+ };
+
+ recognition.onend = () => {
+ recognitionRef.current = null;
+ // Auto-restart if we haven't explicitly stopped.
+ if (shouldListenRef.current) {
+ try {
+ bootRecognition();
+ } catch {
+ shouldListenRef.current = false;
+ setIsListening(false);
+ }
+ return;
+ }
+ setIsListening(false);
+ };
+
+ recognitionRef.current = recognition;
+ recognition.start();
+ }, [speechLang]);
+
+ /** Start browser speech recognition and stream results into the textarea. */
+ const startListening = useCallback(() => {
+ if (!getSpeechRecognition() || speechTemporarilyUnavailable) return;
+
+ // Seed the committed buffer with whatever text is already in the input
+ committedRef.current = input;
+ shouldListenRef.current = true;
+ setIsListening(true);
+
+ bootRecognition();
+ }, [input, bootRecognition, speechTemporarilyUnavailable]);
+
+ // ── Server-side recording helpers ──────────────────────────────
+
+ /** Start capturing audio via MediaRecorder (server transcription mode). */
+ const startRecording = useCallback(async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ streamRef.current = stream;
+ audioChunksRef.current = [];
+
+ const recorder = new MediaRecorder(stream);
+ mediaRecorderRef.current = recorder;
+
+ recorder.ondataavailable = (e) => {
+ if (e.data.size > 0) audioChunksRef.current.push(e.data);
+ };
+
+ recorder.onstop = async () => {
+ const blob = new Blob(audioChunksRef.current, {
+ type: recorder.mimeType,
+ });
+ audioChunksRef.current = [];
+
+ // Release mic
+ streamRef.current?.getTracks().forEach((t) => t.stop());
+ streamRef.current = null;
+
+ if (blob.size === 0) return;
+
+ setIsTranscribing(true);
+ try {
+ const wavBlob = await blobToWav(blob);
+
+ // ── Silence / too-short guard ───────────────────────────
+ // Whisper-family models hallucinate plausible text on silent
+ // audio. Catch it client-side to save an API round-trip.
+ const silent = await isWavSilent(wavBlob);
+ if (silent) {
+ console.log("[chat] Recording was silent — skipping transcription");
+ return; // finally block resets isTranscribing
+ }
+
+ const base64 = await blobToBase64(wavBlob);
+
+ const controller = new AbortController();
+ transcribeAbortRef.current = controller;
+
+ const res = await fetch("/api/transcribe", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ audio: base64,
+ mimeType: "audio/wav",
+ locale,
+ }),
+ signal: controller.signal,
+ });
+
+ const data = await res.json();
+ if (data.text) {
+ setInput((prev) => {
+ const separator = prev.trim() ? " " : "";
+ return prev + separator + data.text;
+ });
+ // Auto-grow textarea
+ requestAnimationFrame(() => {
+ const el = inputRef.current;
+ if (!el) return;
+ el.style.height = "auto";
+ el.style.height = `${el.scrollHeight}px`;
+ });
+ } else if (data.error) {
+ console.warn("[chat] Transcription failed:", data.error);
+ }
+ } catch (err) {
+ if (err instanceof DOMException && err.name === "AbortError") return;
+ console.warn("[chat] Transcription error:", err);
+ } finally {
+ transcribeAbortRef.current = null;
+ setIsTranscribing(false);
+ }
+ };
+
+ recorder.start();
+ setIsRecording(true);
+ setRecordingSeconds(0);
+ recordingTimerRef.current = setInterval(() => {
+ setRecordingSeconds((s) => s + 1);
+ }, 1000);
+ } catch (err) {
+ console.warn("[chat] Microphone access denied:", err);
+ }
+ }, [locale]);
+
+ /** Stop the MediaRecorder — triggers onstop → transcribe flow. */
+ const stopRecording = useCallback(() => {
+ if (recordingTimerRef.current) {
+ clearInterval(recordingTimerRef.current);
+ recordingTimerRef.current = null;
+ }
+ if (mediaRecorderRef.current?.state === "recording") {
+ mediaRecorderRef.current.stop();
+ }
+ setIsRecording(false);
+ }, []);
+
+ if (!open) return null;
+
+ return (
+
+ {/* Header */}
+
+
{t("Chat.title")}
+
+ setSettingsOpen((v) => !v)}
+ className={`transition-colors focus:outline-none ${
+ settingsOpen
+ ? "text-white"
+ : "text-white/80 hover:text-white"
+ }`}
+ aria-label={t("Chat.settings")}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ {/* Settings panel */}
+ {settingsOpen && (
+
+
+
+ {t("Chat.dictationMode")}
+
+ {
+ const next = e.target.value as DictationMode;
+ // Stop any active session before switching
+ if (isListening) stopListening();
+ if (isRecording) stopRecording();
+ setDictationMode(next);
+ }}
+ className="rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-700 focus:border-accent-500 focus:outline-none focus:ring-1 focus:ring-accent-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
+ >
+ {t("Chat.dictationServer")}
+
+ {t("Chat.dictationBrowser")}
+ {(!speechSupported || speechTemporarilyUnavailable)
+ ? ` — ${t("Chat.dictationBrowserUnavailable")}`
+ : ""}
+
+
+
+
+ )}
+
+ {/* Messages area */}
+
+ {messages.length === 0 && (
+
+ {t("Chat.emptyState")}
+
+ )}
+
+ {messages.map((message) => {
+ const isUser = message.role === "user";
+ return (
+
+
+ {message.parts.map((part, i) => {
+ if (part.type === "text") {
+ return {part.text} ;
+ }
+ return null;
+ })}
+
+
+ );
+ })}
+
+ {isLoading &&
+ messages[messages.length - 1]?.role !== "assistant" && (
+
+
+
+ .
+
+ .
+
+
+ .
+
+
+
+
+ )}
+
+ {/* Error banner with retry */}
+ {error && (
+
+
+
+ {t("Chat.errorMessage")}
+
+
{
+ clearError();
+ regenerate();
+ }}
+ className="inline-flex items-center gap-1 shrink-0 rounded-md px-2 py-1 text-xs font-medium text-red-700 hover:bg-red-100 dark:text-red-300 dark:hover:bg-red-900/50 transition-colors"
+ aria-label={t("Chat.retry")}
+ >
+
+ {t("Chat.retry")}
+
+
+
+
+
+
+ )}
+
+
+
+
+ {/* Input area */}
+
+ {
+ setInput(e.target.value);
+ autoResize();
+ }}
+ onKeyDown={handleKeyDown}
+ placeholder={
+ isListening
+ ? t("Chat.listening")
+ : isRecording
+ ? t("Chat.recording", { time: formatRecordingTime(recordingSeconds) })
+ : isTranscribing
+ ? t("Chat.transcribing")
+ : t("Chat.placeholder")
+ }
+ disabled={isLoading || isTranscribing}
+ className={`min-w-0 flex-1 resize-none rounded-md border bg-white px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-1 disabled:opacity-60 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 transition-colors max-h-32 overflow-y-auto ${
+ isListening || isRecording
+ ? "border-red-400 focus:border-red-500 focus:ring-red-400 dark:border-red-500"
+ : "border-gray-300 focus:border-accent-500 focus:ring-accent-500 dark:border-gray-600"
+ }`}
+ />
+ {/* Mic button — dispatches to browser or server dictation mode */}
+ {
+ if (dictationMode === "browser") {
+ if (isListening) stopListening(); else startListening();
+ } else {
+ if (isRecording) stopRecording(); else startRecording();
+ }
+ }}
+ disabled={
+ isLoading ||
+ isTranscribing ||
+ (dictationMode === "browser" &&
+ (!speechSupported || speechTemporarilyUnavailable))
+ }
+ className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-md transition-colors focus:outline-none disabled:opacity-40 disabled:cursor-not-allowed ${
+ isListening || isRecording
+ ? "bg-red-600 text-white hover:bg-red-700"
+ : isTranscribing
+ ? "bg-amber-500 text-white animate-pulse"
+ : "border border-gray-300 text-gray-500 hover:text-accent-600 hover:border-accent-400 dark:border-gray-600 dark:text-gray-400 dark:hover:text-accent-400 dark:hover:border-accent-500"
+ }`}
+ aria-label={
+ isListening
+ ? t("Chat.stopListening")
+ : isRecording
+ ? t("Chat.stopRecording")
+ : isTranscribing
+ ? t("Chat.transcribing")
+ : dictationMode === "browser"
+ ? t("Chat.startListening")
+ : t("Chat.startRecording")
+ }
+ >
+ {isListening || isRecording ? (
+
+ ) : (
+
+ )}
+
+ {/* Send button */}
+
+
+
+
+
+ );
+}
diff --git a/src/components/GalleryContainer.tsx b/src/components/GalleryContainer.tsx
new file mode 100644
index 0000000..6c93013
--- /dev/null
+++ b/src/components/GalleryContainer.tsx
@@ -0,0 +1,112 @@
+"use client";
+
+import { useState } from "react";
+import { CategorySection } from "@/types/artwork";
+import { PersonalMessage } from "@/types/config";
+import SectionContainer from "./SectionContainer";
+import { XMarkIcon } from "@heroicons/react/24/outline";
+import { useTranslations } from "next-intl";
+
+// Function to determine time-based greeting
+function getTimeBasedGreeting(): string {
+ const hour = new Date().getHours();
+
+ if (hour >= 5 && hour < 12) {
+ return "morning";
+ } else if (hour >= 12 && hour < 18) {
+ return "afternoon";
+ } else if (hour >= 18 && hour < 22) {
+ return "evening";
+ } else {
+ return "default";
+ }
+}
+
+interface GalleryContainerProps {
+ initialSections: CategorySection[];
+ /** Personal message loaded server-side from the tenant's data directory. */
+ personalMessage: PersonalMessage;
+}
+
+export default function GalleryContainer({
+ initialSections,
+ personalMessage,
+}: GalleryContainerProps) {
+ const [isNoteVisible, setIsNoteVisible] = useState(true);
+ const [isAnimatingOut, setIsAnimatingOut] = useState(false);
+ // Use translations for UI elements (artist greeting/description)
+ const t = useTranslations();
+
+ const handleCloseNote = () => {
+ setIsAnimatingOut(true);
+ // Wait for animation to complete before hiding
+ setTimeout(() => {
+ setIsNoteVisible(false);
+ setIsAnimatingOut(false);
+ }, 300); // Match the animation duration
+ };
+
+ return (
+
+
+
+
+
+
+ {/* Artist greeting and description are translatable UI content */}
+
+ {t(`greeting.${getTimeBasedGreeting()}`)}{" "}
+ {t("introduction", { name: t("Artist.name") })}
+
+
{t("Artist.description")}
+
+
+ {/* Personal message uses original data, NOT translations
+ Reasoning: This is Nini's personal message to Tibik - it's her authentic voice
+ and personal content, not a UI element that should be translated */}
+ {personalMessage.enabled && isNoteVisible && (
+
+ {personalMessage.dismissible && (
+
+
+
+ )}
+ {personalMessage.recipient && (
+
+ {personalMessage.recipient}
+
+ )}
+
+ {personalMessage.message}
+
+
+ )}
+
+
+
+
+
+ {initialSections.length > 0 ? (
+ initialSections.map((section) => (
+
+ ))
+ ) : (
+
+
+ {t("Search.noResults")}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..47b8dff
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,273 @@
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import {
+ MagnifyingGlassIcon,
+ ArrowPathIcon,
+ UserIcon,
+ ChevronDownIcon,
+} from "@heroicons/react/24/outline";
+import Link from "next/link";
+import { useRouter, usePathname, useSearchParams } from "next/navigation";
+import { useTranslations, useLocale } from "next-intl";
+import LanguageSwitcher from "./LanguageSwitcher";
+import { useHeaderHeightCSS } from "@/hooks/useHeaderHeight";
+
+interface HeaderProps {
+ // Optional props for controlled mode (like SearchHeader)
+ currentSearch?: string;
+ onSearch?: (searchTerm: string) => void;
+ isLoading?: boolean;
+}
+
+export default function Header({
+ currentSearch,
+ onSearch,
+ isLoading: externalLoading,
+}: HeaderProps) {
+ const [searchTerm, setSearchTerm] = useState("");
+ const [isVisible, setIsVisible] = useState(true);
+ const [lastScrollY, setLastScrollY] = useState(0);
+ const [internalLoading, setInternalLoading] = useState(false);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [adminEmail, setAdminEmail] = useState(null);
+ const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
+ const [isLoggingOut, setIsLoggingOut] = useState(false);
+
+ const headerRef = useRef(null);
+ const userMenuRef = useRef(null);
+ useHeaderHeightCSS(headerRef);
+
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+ const locale = useLocale();
+ const t = useTranslations();
+
+ // Determine if we're in controlled mode (SearchHeader behavior) or autonomous mode (GlobalHeader behavior)
+ const isControlled = currentSearch !== undefined && onSearch !== undefined;
+ const isLoading = externalLoading ?? internalLoading;
+
+ // Initialize search term
+ useEffect(() => {
+ if (isControlled) {
+ setSearchTerm(currentSearch || "");
+ } else {
+ const urlSearch = searchParams.get("search") || "";
+ setSearchTerm(urlSearch);
+ }
+ }, [isControlled, currentSearch, searchParams]);
+
+ // Check admin auth status
+ useEffect(() => {
+ const checkAuth = async () => {
+ try {
+ const response = await fetch("/api/admin/auth/session");
+ const data = await response.json();
+ if (data.authenticated) {
+ setIsAuthenticated(true);
+ setAdminEmail(data.email || null);
+ }
+ } catch {
+ // Silently fail - just show the login button
+ }
+ };
+ checkAuth();
+ }, []);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ isUserMenuOpen &&
+ userMenuRef.current &&
+ !userMenuRef.current.contains(event.target as Node)
+ ) {
+ setIsUserMenuOpen(false);
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, [isUserMenuOpen]);
+
+ // Scroll behavior
+ useEffect(() => {
+ const handleScroll = () => {
+ const currentScrollY = window.scrollY;
+
+ // Show header when scrolling up or at the top
+ if (currentScrollY < lastScrollY || currentScrollY < 10) {
+ setIsVisible(true);
+ }
+ // Hide header when scrolling down and past a threshold
+ else if (currentScrollY > lastScrollY && currentScrollY > 100) {
+ setIsVisible(false);
+ }
+
+ setLastScrollY(currentScrollY);
+ };
+
+ window.addEventListener("scroll", handleScroll, { passive: true });
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, [lastScrollY]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ const trimmedSearch = searchTerm.trim();
+
+ if (isControlled) {
+ // Controlled mode: use provided onSearch callback
+ onSearch!(trimmedSearch);
+ } else {
+ // Autonomous mode: handle navigation ourselves
+ setInternalLoading(true);
+ try {
+ // Check if we're on the localized home page
+ const isOnHomePage = pathname === `/${locale}`;
+
+ if (!isOnHomePage) {
+ // Navigate to localized home with search
+ if (trimmedSearch) {
+ router.push(
+ `/${locale}?search=${encodeURIComponent(trimmedSearch)}`,
+ );
+ } else {
+ router.push(`/${locale}`);
+ }
+ } else {
+ // If we're on the home page, update search params
+ const params = new URLSearchParams(searchParams.toString());
+ if (trimmedSearch) {
+ params.set("search", trimmedSearch);
+ } else {
+ params.delete("search");
+ }
+ router.push(`/${locale}?${params.toString()}`);
+ }
+ } finally {
+ setInternalLoading(false);
+ }
+ }
+ };
+
+ const handleLogout = async () => {
+ try {
+ setIsLoggingOut(true);
+ await fetch("/api/admin/auth/logout", { method: "POST" });
+ } finally {
+ setIsAuthenticated(false);
+ setAdminEmail(null);
+ setIsUserMenuOpen(false);
+ setIsLoggingOut(false);
+ router.push(`/${locale}/admin/login`);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx
new file mode 100644
index 0000000..db2675b
--- /dev/null
+++ b/src/components/LanguageSwitcher.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import { useLocale } from "next-intl";
+import { useState } from "react";
+import { useRouter, usePathname } from "@/i18n/navigation";
+import { GlobeAltIcon } from "@heroicons/react/24/outline";
+import { SUPPORTED_LOCALES, getLocaleConfig } from "@/lib/locales";
+
+export default function LanguageSwitcher() {
+ const locale = useLocale();
+ const router = useRouter();
+ const pathname = usePathname();
+ const [isOpen, setIsOpen] = useState(false);
+
+ const handleLanguageChange = (newLocale: string) => {
+ // Navigate to the same path under the new locale.
+ // The next-intl middleware handles cookie management automatically.
+ router.replace(pathname, { locale: newLocale });
+ };
+
+ const currentLanguage = getLocaleConfig(locale) || SUPPORTED_LOCALES[0];
+
+ return (
+
+
setIsOpen(!isOpen)}
+ className="flex items-center space-x-2 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
+ aria-label="Change language"
+ >
+
+ {currentLanguage.flag}
+ {currentLanguage.name}
+
+
+ {isOpen && (
+
+
+ {SUPPORTED_LOCALES.map((language) => (
+ {
+ handleLanguageChange(language.code);
+ setIsOpen(false);
+ }}
+ className={`
+ w-full text-left px-4 py-2 text-sm flex items-center space-x-3
+ ${
+ language.code === locale
+ ? "bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white"
+ : "text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
+ }
+ transition-colors
+ `}
+ >
+ {language.flag}
+ {language.name}
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/LocaleHtmlLang.tsx b/src/components/LocaleHtmlLang.tsx
new file mode 100644
index 0000000..71721cf
--- /dev/null
+++ b/src/components/LocaleHtmlLang.tsx
@@ -0,0 +1,14 @@
+"use client";
+
+import { useLocale } from "next-intl";
+import { useEffect } from "react";
+
+export default function LocaleHtmlLang() {
+ const locale = useLocale();
+
+ useEffect(() => {
+ document.documentElement.lang = locale;
+ }, [locale]);
+
+ return null;
+}
diff --git a/src/components/SectionContainer.tsx b/src/components/SectionContainer.tsx
new file mode 100644
index 0000000..9081880
--- /dev/null
+++ b/src/components/SectionContainer.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import { useState, useMemo } from "react";
+import { CategorySection, Artwork } from "@/types/artwork";
+import SectionHeader from "./SectionHeader";
+import ArtworkGrid from "./ArtworkGrid";
+
+interface SectionContainerProps {
+ section: CategorySection;
+}
+
+export default function SectionContainer({ section }: SectionContainerProps) {
+ const [sortConfig, setSortConfig] = useState<{
+ key: string;
+ order: "asc" | "desc";
+ } | null>(null);
+
+ const sortedArtworks = useMemo(() => {
+ if (!sortConfig) return section.artworks;
+
+ return [...section.artworks].sort((a: Artwork, b: Artwork) => {
+ const aValue = a[sortConfig.key as keyof Artwork];
+ const bValue = b[sortConfig.key as keyof Artwork];
+
+ if (aValue === undefined || bValue === undefined) return 0;
+
+ const comparison =
+ typeof aValue === "string" && typeof bValue === "string"
+ ? aValue.localeCompare(bValue)
+ : aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
+
+ return sortConfig.order === "asc" ? comparison : -comparison;
+ });
+ }, [section.artworks, sortConfig]);
+
+ const handleSort = (sortBy: string, order: "asc" | "desc") => {
+ setSortConfig({ key: sortBy, order });
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/SectionHeader.tsx b/src/components/SectionHeader.tsx
new file mode 100644
index 0000000..3537c1d
--- /dev/null
+++ b/src/components/SectionHeader.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import { useState } from "react";
+import { ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/24/outline";
+import { useTranslations } from "next-intl";
+
+interface SectionHeaderProps {
+ id: string;
+ title: string;
+ description: string;
+ onSort: (sortBy: string, order: "asc" | "desc") => void;
+}
+
+export default function SectionHeader({
+ id,
+ title,
+ description,
+ onSort,
+}: SectionHeaderProps) {
+ const [sortConfig, setSortConfig] = useState<{
+ key: string;
+ order: "asc" | "desc";
+ } | null>(null);
+ const t = useTranslations();
+
+ const handleSort = (key: string) => {
+ const isAsc = sortConfig?.key === key && sortConfig.order === "asc";
+ const newOrder = isAsc ? "desc" : "asc";
+ setSortConfig({ key, order: newOrder });
+ onSort(key, newOrder);
+ };
+
+ const getSortIcon = (key: string) => {
+ if (sortConfig?.key !== key) {
+ return null;
+ }
+ return sortConfig.order === "asc" ? (
+
+ ) : (
+
+ );
+ };
+
+ return (
+
+
+
+ {title}
+
+
+ handleSort("title")}
+ className="flex items-center gap-1 px-2 py-1 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
+ title={t("Sort.sortByTitle")}
+ >
+ {t("Sort.title")}
+ {getSortIcon("title")}
+
+ handleSort("year")}
+ className="flex items-center gap-1 px-2 py-1 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
+ title={t("Sort.sortByYear")}
+ >
+ {t("Sort.year")}
+ {getSortIcon("year")}
+
+
+
+
{description}
+
+ );
+}
diff --git a/src/components/admin/FormField.tsx b/src/components/admin/FormField.tsx
new file mode 100644
index 0000000..ab57944
--- /dev/null
+++ b/src/components/admin/FormField.tsx
@@ -0,0 +1,39 @@
+import { ReactNode } from "react";
+
+interface FormFieldProps {
+ label: string;
+ htmlFor: string;
+ children: ReactNode;
+ helperText?: string;
+ characterCount?: {
+ current: number;
+ max: number;
+ };
+}
+
+export default function FormField({
+ label,
+ htmlFor,
+ children,
+ helperText,
+ characterCount,
+}: FormFieldProps) {
+ return (
+
+
+ {label}
+
+ {children}
+ {(helperText || characterCount) && (
+
+ {characterCount
+ ? `${characterCount.current}/${characterCount.max} characters`
+ : helperText}
+
+ )}
+
+ );
+}
diff --git a/src/components/admin/SaveButton.tsx b/src/components/admin/SaveButton.tsx
new file mode 100644
index 0000000..895ea0a
--- /dev/null
+++ b/src/components/admin/SaveButton.tsx
@@ -0,0 +1,25 @@
+interface SaveButtonProps {
+ isLoading: boolean;
+ loadingText: string;
+ saveText: string;
+ disabled?: boolean;
+}
+
+export default function SaveButton({
+ isLoading,
+ loadingText,
+ saveText,
+ disabled,
+}: SaveButtonProps) {
+ return (
+
+
+ {isLoading ? loadingText : saveText}
+
+
+ );
+}
diff --git a/src/components/admin/SettingsCard.tsx b/src/components/admin/SettingsCard.tsx
new file mode 100644
index 0000000..5fb61c5
--- /dev/null
+++ b/src/components/admin/SettingsCard.tsx
@@ -0,0 +1,48 @@
+import { ReactNode } from "react";
+
+interface SettingsCardProps {
+ title: string;
+ description: string;
+ error?: string;
+ success?: string;
+ children: ReactNode;
+}
+
+export default function SettingsCard({
+ title,
+ description,
+ error,
+ success,
+ children,
+}: SettingsCardProps) {
+ return (
+
+
+
+ {title}
+
+
+ {description}
+
+
+ {error && (
+
+ )}
+
+ {success && (
+
+ )}
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/theme/ThemeProvider.tsx b/src/components/theme/ThemeProvider.tsx
new file mode 100644
index 0000000..a0121a7
--- /dev/null
+++ b/src/components/theme/ThemeProvider.tsx
@@ -0,0 +1,144 @@
+"use client";
+
+import React, { createContext, useContext, useEffect, useState } from "react";
+import {
+ AccentColor,
+ ColorScheme,
+ ThemeConfig,
+ ThemeContextType,
+} from "@/types/theme";
+
+const ThemeContext = createContext(undefined);
+
+export function useTheme() {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error("useTheme must be used within a ThemeProvider");
+ }
+ return context;
+}
+
+interface ThemeProviderProps {
+ children: React.ReactNode;
+}
+
+export function ThemeProvider({ children }: ThemeProviderProps) {
+ const [theme, setTheme] = useState({
+ accent: "pink",
+ colorScheme: "system",
+ });
+ const [isClient, setIsClient] = useState(false);
+ const [systemPrefersDark, setSystemPrefersDark] = useState(false);
+
+ // Load theme from localStorage and detect system preference
+ useEffect(() => {
+ setIsClient(true);
+
+ // Detect system preference
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ setSystemPrefersDark(mediaQuery.matches);
+
+ // Listen for system preference changes
+ const handleChange = (e: MediaQueryListEvent) => {
+ setSystemPrefersDark(e.matches);
+ };
+
+ mediaQuery.addEventListener("change", handleChange);
+
+ // Load saved theme
+ const savedTheme = localStorage.getItem("nini-gallery-theme");
+ if (savedTheme) {
+ try {
+ const parsedTheme = JSON.parse(savedTheme);
+ // Ensure colorScheme exists for backward compatibility
+ if (!parsedTheme.colorScheme) {
+ parsedTheme.colorScheme = "system";
+ }
+ setTheme(parsedTheme);
+ } catch {
+ console.warn("Failed to parse saved theme, using default");
+ }
+ }
+
+ return () => {
+ mediaQuery.removeEventListener("change", handleChange);
+ };
+ }, []);
+
+ // Calculate resolved color scheme
+ const resolvedColorScheme: "light" | "dark" =
+ theme.colorScheme === "system"
+ ? systemPrefersDark
+ ? "dark"
+ : "light"
+ : theme.colorScheme;
+
+ // Update CSS variables and dark mode class when theme changes
+ useEffect(() => {
+ if (!isClient) return;
+
+ const root = document.documentElement;
+
+ // Apply dark mode class
+ if (resolvedColorScheme === "dark") {
+ root.classList.add("dark");
+ } else {
+ root.classList.remove("dark");
+ }
+
+ // Define color mappings for each accent color
+ const colorMappings = {
+ pink: {
+ "--accent-50": "oklch(0.971 0.014 343.198)", // pink-50
+ "--accent-100": "oklch(0.948 0.028 342.258)", // pink-100
+ "--accent-200": "oklch(0.899 0.061 343.231)", // pink-200
+ "--accent-500": "oklch(0.656 0.241 354.308)", // pink-500
+ "--accent-600": "oklch(0.592 0.249 0.584)", // pink-600
+ },
+ orange: {
+ "--accent-50": "oklch(0.98 0.016 73.684)", // orange-50
+ "--accent-100": "oklch(0.954 0.038 75.164)", // orange-100
+ "--accent-200": "oklch(0.901 0.076 70.697)", // orange-200
+ "--accent-500": "oklch(0.705 0.213 47.604)", // orange-500
+ "--accent-600": "oklch(0.646 0.222 41.116)", // orange-600
+ },
+ green: {
+ "--accent-50": "oklch(0.986 0.031 120.757)", // lime-50
+ "--accent-100": "oklch(0.967 0.067 122.328)", // lime-100
+ "--accent-200": "oklch(0.938 0.127 124.321)", // lime-200
+ "--accent-500": "oklch(0.768 0.233 130.85)", // lime-500
+ "--accent-600": "oklch(0.648 0.2 131.684)", // lime-600
+ },
+ };
+
+ const colors = colorMappings[theme.accent];
+ Object.entries(colors).forEach(([property, value]) => {
+ root.style.setProperty(property, value);
+ });
+
+ // Save to localStorage
+ localStorage.setItem("nini-gallery-theme", JSON.stringify(theme));
+ }, [theme, resolvedColorScheme, isClient]);
+
+ const setAccentColor = (color: AccentColor) => {
+ setTheme((prev) => ({ ...prev, accent: color }));
+ };
+
+ const setColorScheme = (colorScheme: ColorScheme) => {
+ setTheme((prev) => ({ ...prev, colorScheme }));
+ };
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/theme/ThemeSelector.tsx b/src/components/theme/ThemeSelector.tsx
new file mode 100644
index 0000000..763a874
--- /dev/null
+++ b/src/components/theme/ThemeSelector.tsx
@@ -0,0 +1,253 @@
+"use client";
+
+import { useState, useRef, useEffect } from "react";
+import { useTheme } from "./ThemeProvider";
+import {
+ ACCENT_COLORS,
+ COLOR_SCHEMES,
+ AccentColor,
+ ColorScheme,
+} from "@/types/theme";
+import { SwatchIcon } from "@heroicons/react/24/outline";
+import { useTranslations } from "next-intl";
+
+export default function ThemeSelector() {
+ const { theme, setAccentColor, setColorScheme, systemPrefersDark } =
+ useTheme();
+ const [isOpen, setIsOpen] = useState(false);
+ const [isAnimating, setIsAnimating] = useState(false);
+ const dropdownRef = useRef(null);
+ const buttonRef = useRef(null);
+ const t = useTranslations();
+
+ // Close dropdown when clicking outside
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (
+ dropdownRef.current &&
+ buttonRef.current &&
+ !dropdownRef.current.contains(event.target as Node) &&
+ !buttonRef.current.contains(event.target as Node)
+ ) {
+ setIsOpen(false);
+ }
+ }
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, []);
+
+ // Close dropdown on escape key
+ useEffect(() => {
+ function handleEscape(event: KeyboardEvent) {
+ if (event.key === "Escape") {
+ setIsOpen(false);
+ }
+ }
+
+ if (isOpen) {
+ document.addEventListener("keydown", handleEscape);
+ return () => {
+ document.removeEventListener("keydown", handleEscape);
+ };
+ }
+ }, [isOpen]);
+
+ // Radial animation effect every 15 seconds when closed
+ useEffect(() => {
+ const interval = setInterval(() => {
+ if (!isOpen) {
+ // Only animate when dropdown is closed
+ setIsAnimating(true);
+ setTimeout(() => setIsAnimating(false), 2000); // Animation duration
+ }
+ }, 15000); // Every 15 seconds
+
+ return () => clearInterval(interval);
+ }, [isOpen]); // Include isOpen dependency
+
+ const handleColorSelect = (color: AccentColor) => {
+ setAccentColor(color);
+ setIsOpen(false);
+ };
+
+ const handleColorSchemeSelect = (scheme: ColorScheme) => {
+ setColorScheme(scheme);
+ setIsOpen(false);
+ };
+
+ // Unified glow ring configuration
+ const glowRings = [
+ { opacity: "/50", blur: "", scale: isOpen ? "scale-200" : "", delay: "0s" },
+ {
+ opacity: "/35",
+ blur: "blur-[1px]",
+ scale: isOpen ? "scale-175" : "",
+ delay: "0.3s",
+ },
+ {
+ opacity: "/20",
+ blur: "blur-[2px]",
+ scale: isOpen ? "scale-150" : "",
+ delay: "0.6s",
+ },
+ ];
+
+ const shouldShowGlow = isOpen || isAnimating;
+
+ return (
+
+ {/* Theme selector button with unified radial glow system */}
+
+ {/* Unified glow rings - permanent when open, animated when pulsing */}
+ {shouldShowGlow &&
+ glowRings.map((ring, index) => (
+
+ ))}
+
+
setIsOpen(!isOpen)}
+ className={`relative z-10 w-12 h-12 bg-accent-500 shadow-lg rounded-full border border-accent-600 hover:bg-accent-600 hover:shadow-xl transition-all duration-200 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-500 flex items-center justify-center ${
+ shouldShowGlow ? "scale-105" : ""
+ }`}
+ aria-label={t("Theme.changeThemeColors")}
+ aria-expanded={isOpen}
+ aria-haspopup="true"
+ >
+
+
+
+
+ {/* Theme selection dropdown */}
+ {isOpen && (
+
+ {/* Color Scheme Section */}
+
+
+ {t("Theme.appearance")}
+
+
+
+ {Object.entries(COLOR_SCHEMES).map(([schemeKey, schemeInfo]) => {
+ const scheme = schemeKey as ColorScheme;
+ const isSelected = theme.colorScheme === scheme;
+
+ return (
+
handleColorSchemeSelect(scheme)}
+ className={`w-full flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors duration-150 ${
+ isSelected
+ ? "bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+ : "text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
+ }`}
+ role="menuitem"
+ aria-current={isSelected ? "true" : "false"}
+ >
+ {/* Scheme icon */}
+
+ {schemeInfo.icon}
+
+
+
+ {t(`Theme.schemes.${scheme}`)}
+
+
+ {/* Show actual system preference for system option */}
+ {scheme === "system" && (
+
+ (
+ {systemPrefersDark ? t("Theme.dark") : t("Theme.light")}
+ )
+
+ )}
+
+ {/* Selected indicator */}
+ {isSelected && (
+
+ )}
+
+ );
+ })}
+
+
+
+ {/* Divider */}
+
+
+ {/* Accent Color Section */}
+
+
+ {t("Theme.accentColor")}
+
+
+
+ {Object.entries(ACCENT_COLORS).map(([colorKey, colorInfo]) => {
+ const color = colorKey as AccentColor;
+ const isSelected = theme.accent === color;
+
+ return (
+
handleColorSelect(color)}
+ className={`w-full flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors duration-150 ${
+ isSelected
+ ? "bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100"
+ : "text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"
+ }`}
+ role="menuitem"
+ aria-current={isSelected ? "true" : "false"}
+ >
+ {/* Color preview circle */}
+
+
+
+ {t(`Theme.colors.${color}`)}
+
+
+ {/* Selected indicator */}
+ {isSelected && (
+
+ )}
+
+ );
+ })}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/hooks/useHeaderHeight.ts b/src/hooks/useHeaderHeight.ts
new file mode 100644
index 0000000..4da2c61
--- /dev/null
+++ b/src/hooks/useHeaderHeight.ts
@@ -0,0 +1,62 @@
+"use client";
+
+import { useState, useEffect, RefObject } from "react";
+
+/**
+ * Custom hook to dynamically measure header height
+ * @param headerRef - React ref to the header element
+ * @returns The current height of the header in pixels
+ */
+function useHeaderHeight(headerRef: RefObject) {
+ const [headerHeight, setHeaderHeight] = useState(0);
+
+ useEffect(() => {
+ const updateHeight = () => {
+ if (headerRef.current) {
+ const height = headerRef.current.offsetHeight;
+ setHeaderHeight(height);
+ }
+ };
+
+ // Initial measurement
+ updateHeight();
+
+ // Create ResizeObserver to watch for header size changes
+ const resizeObserver = new ResizeObserver(() => {
+ updateHeight();
+ });
+
+ if (headerRef.current) {
+ resizeObserver.observe(headerRef.current);
+ }
+
+ // Also listen for window resize events as fallback
+ window.addEventListener("resize", updateHeight);
+
+ return () => {
+ resizeObserver.disconnect();
+ window.removeEventListener("resize", updateHeight);
+ };
+ }, [headerRef]);
+
+ return headerHeight;
+}
+
+/**
+ * Custom hook to create and manage header height CSS variable
+ * @param headerRef - React ref to the header element
+ * @returns The current height of the header in pixels
+ */
+export function useHeaderHeightCSS(headerRef: RefObject) {
+ const headerHeight = useHeaderHeight(headerRef);
+
+ useEffect(() => {
+ // Update CSS custom property for use in styles
+ document.documentElement.style.setProperty(
+ "--header-height",
+ `${headerHeight}px`,
+ );
+ }, [headerHeight]);
+
+ return headerHeight;
+}
diff --git a/src/i18n/navigation.ts b/src/i18n/navigation.ts
new file mode 100644
index 0000000..fd8ba9a
--- /dev/null
+++ b/src/i18n/navigation.ts
@@ -0,0 +1,5 @@
+import { createNavigation } from "next-intl/navigation";
+import { routing } from "./routing";
+
+export const { Link, redirect, usePathname, useRouter } =
+ createNavigation(routing);
diff --git a/src/i18n/request.ts b/src/i18n/request.ts
new file mode 100644
index 0000000..298863e
--- /dev/null
+++ b/src/i18n/request.ts
@@ -0,0 +1,50 @@
+import { promises as fs } from "fs";
+import { getRequestConfig } from "next-intl/server";
+import { routing } from "./routing";
+import { getArtistData } from "@/lib/artist-data";
+import { getTenantId, tenantMessagePath } from "@/lib/tenant";
+
+export default getRequestConfig(async ({ requestLocale }) => {
+ // The locale is resolved by the next-intl middleware from the URL prefix,
+ // locale cookie, or accept-language negotiation. It should always be present.
+ const requested = await requestLocale;
+
+ if (!requested || !routing.locales.includes(requested as (typeof routing.locales)[number])) {
+ throw new Error(
+ `[i18n/request] Invalid or missing locale "${requested}". ` +
+ "The next-intl middleware should have resolved this before reaching here.",
+ );
+ }
+
+ const locale = requested;
+
+ // Resolve the tenant from the proxy-injected header.
+ // Falls back to the default tenant during static prerendering.
+ const tenantId = await getTenantId();
+
+ // Load UI and admin messages (shared across all tenants — static import paths)
+ // Load artwork messages (tenant-scoped — read via fs)
+ const [uiMessages, adminMessages, artworkMessagesRaw] = await Promise.all([
+ import(`../../messages/ui/${locale}.json`),
+ import(`../../messages/ui/admin/${locale}.json`),
+ fs.readFile(
+ tenantMessagePath("artworks", tenantId, `${locale}.json`),
+ "utf-8",
+ ),
+ ]);
+
+ const artworkMessages = JSON.parse(artworkMessagesRaw);
+
+ // Get artist data using hybrid approach (user data + translations + static fallback)
+ const artistData = await getArtistData(tenantId, locale);
+
+ return {
+ locale,
+ messages: {
+ ...uiMessages.default,
+ ...artworkMessages,
+ Artist: artistData,
+ admin: adminMessages.default,
+ },
+ };
+});
diff --git a/src/i18n/routing.ts b/src/i18n/routing.ts
new file mode 100644
index 0000000..e3557a8
--- /dev/null
+++ b/src/i18n/routing.ts
@@ -0,0 +1,13 @@
+import { defineRouting } from "next-intl/routing";
+import { SUPPORTED_LOCALES } from "@/lib/locales";
+
+const locales = SUPPORTED_LOCALES.map((l) => l.code);
+
+export const routing = defineRouting({
+ locales,
+ defaultLocale: "en",
+ localeCookie: {
+ name: "locale",
+ maxAge: 60 * 60 * 24 * 365, // 1 year, matching current LanguageSwitcher
+ },
+});
diff --git a/src/lib/admin-auth.ts b/src/lib/admin-auth.ts
new file mode 100644
index 0000000..7b6781f
--- /dev/null
+++ b/src/lib/admin-auth.ts
@@ -0,0 +1,234 @@
+import nodemailer from "nodemailer";
+import jwt from "jsonwebtoken";
+import crypto from "crypto";
+import { OTPSession, AdminSession } from "@/types/admin";
+import { getEmailTranslations, getLocalizedSiteName } from "./admin-i18n";
+
+// In-memory storage for OTP sessions (in production, use Redis or database)
+const otpSessions = new Map();
+
+// Rate limiting storage (email -> { count, resetTime })
+const rateLimits = new Map();
+
+// Check if admin is properly configured
+export function isAdminConfigured(): boolean {
+ const required = [
+ "SMTP_HOST",
+ "SMTP_PORT",
+ "SMTP_USER",
+ "SMTP_PASS",
+ "SMTP_SECURE",
+ "SMTP_REQUIRE_TLS",
+ "ADMIN_EMAIL",
+ "JWT_SECRET",
+ ];
+ return required.every((key) => process.env[key]);
+}
+
+// Get missing environment variables
+export function getMissingEnvVars(): string[] {
+ const required = [
+ "SMTP_HOST",
+ "SMTP_PORT",
+ "SMTP_USER",
+ "SMTP_PASS",
+ "SMTP_SECURE",
+ "SMTP_REQUIRE_TLS",
+ "ADMIN_EMAIL",
+ "JWT_SECRET",
+ ];
+ return required.filter((key) => !process.env[key]);
+}
+
+// Configuration from environment variables
+const SMTP_CONFIG = {
+ host: process.env.SMTP_HOST!,
+ port: parseInt(process.env.SMTP_PORT!),
+ secure: process.env.SMTP_SECURE! === "true", // SSL/TLS from start (port 465)
+ requireTLS: process.env.SMTP_REQUIRE_TLS! === "true", // Force STARTTLS upgrade
+ auth: {
+ user: process.env.SMTP_USER!,
+ pass: process.env.SMTP_PASS!,
+ },
+};
+
+const ADMIN_EMAIL = process.env.ADMIN_EMAIL!;
+const JWT_SECRET = process.env.JWT_SECRET!;
+const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME;
+
+// Create nodemailer transporter
+const transporter = nodemailer.createTransport(SMTP_CONFIG);
+
+// Generate 6-digit OTP
+export function generateOTP(): string {
+ return crypto.randomInt(100000, 999999).toString();
+}
+
+// Check rate limiting (max 3 OTP requests per 15 minutes per email)
+export function checkRateLimit(email: string): boolean {
+ const now = Date.now();
+ const limit = rateLimits.get(email);
+
+ if (!limit || now > limit.resetTime) {
+ // Reset or create new limit
+ rateLimits.set(email, { count: 1, resetTime: now + 15 * 60 * 1000 }); // 15 minutes
+ return true;
+ }
+
+ if (limit.count >= 3) {
+ return false; // Rate limited
+ }
+
+ limit.count++;
+ return true;
+}
+
+// Send OTP email
+export async function sendOTPEmail(
+ email: string,
+ otp: string,
+ locale: string,
+): Promise {
+ try {
+ const emailTranslations = await getEmailTranslations(locale);
+ const localizedSiteName = await getLocalizedSiteName(locale);
+ const senderName = SMTP_FROM_NAME || localizedSiteName;
+
+ const mailOptions = {
+ from: `${senderName} <${SMTP_CONFIG.auth.user}>`,
+ to: email,
+ subject: emailTranslations.subject,
+ html: `
+
+
${emailTranslations.title}
+
${emailTranslations.codeMessage}
+
+ ${otp}
+
+
${emailTranslations.expiryMessage}
+
${emailTranslations.ignoreMessage}
+
+
${emailTranslations.automatedMessage.replace("{siteName}", localizedSiteName)}
+
+ `,
+ text: emailTranslations.textVersion.replace("{otp}", otp),
+ };
+
+ await transporter.sendMail(mailOptions);
+ return true;
+ } catch (error) {
+ console.error("Failed to send OTP email:", error);
+ return false;
+ }
+}
+
+// Store OTP session
+export function storeOTPSession(email: string, otp: string): void {
+ const expiresAt = Date.now() + 10 * 60 * 1000; // 10 minutes
+ otpSessions.set(email, {
+ email,
+ otp,
+ expiresAt,
+ attempts: 0,
+ });
+}
+
+// Verify OTP
+export function verifyOTP(email: string, providedOTP: string): boolean {
+ const session = otpSessions.get(email);
+
+ if (!session) {
+ return false; // No session found
+ }
+
+ if (Date.now() > session.expiresAt) {
+ otpSessions.delete(email);
+ return false; // Expired
+ }
+
+ session.attempts++;
+
+ if (session.attempts > 3) {
+ otpSessions.delete(email);
+ return false; // Too many attempts
+ }
+
+ if (session.otp === providedOTP) {
+ otpSessions.delete(email); // Clean up successful session
+ return true;
+ }
+
+ return false; // Wrong OTP
+}
+
+// Generate JWT token for admin session
+export function generateAdminToken(email: string): string {
+ const payload: AdminSession = {
+ email,
+ issuedAt: Date.now(),
+ expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
+ };
+
+ return jwt.sign(payload, JWT_SECRET, { expiresIn: "24h" });
+}
+
+// Verify JWT token
+export function verifyAdminToken(token: string): AdminSession | null {
+ try {
+ const decoded = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload;
+
+ // Validate the decoded token structure
+ if (!decoded.email || !decoded.issuedAt || !decoded.expiresAt) {
+ return null;
+ }
+
+ const adminSession: AdminSession = {
+ email: decoded.email,
+ issuedAt: decoded.issuedAt,
+ expiresAt: decoded.expiresAt,
+ };
+
+ // Check if token is expired
+ if (Date.now() > adminSession.expiresAt) {
+ return null;
+ }
+
+ // Check if email is authorized
+ if (adminSession.email !== ADMIN_EMAIL) {
+ return null;
+ }
+
+ return adminSession;
+ } catch {
+ return null;
+ }
+}
+
+// Check if email is authorized for admin access
+export function isAuthorizedEmail(email: string): boolean {
+ return email === ADMIN_EMAIL;
+}
+
+// Clean up expired sessions (call periodically)
+function cleanupExpiredSessions(): void {
+ const now = Date.now();
+
+ // Clean up OTP sessions
+ for (const [email, session] of otpSessions.entries()) {
+ if (now > session.expiresAt) {
+ otpSessions.delete(email);
+ }
+ }
+
+ // Clean up rate limits
+ for (const [email, limit] of rateLimits.entries()) {
+ if (now > limit.resetTime) {
+ rateLimits.delete(email);
+ }
+ }
+}
+
+// Initialize cleanup interval (run every 5 minutes)
+if (typeof window === "undefined") {
+ setInterval(cleanupExpiredSessions, 5 * 60 * 1000);
+}
diff --git a/src/lib/admin-i18n.ts b/src/lib/admin-i18n.ts
new file mode 100644
index 0000000..3d735ce
--- /dev/null
+++ b/src/lib/admin-i18n.ts
@@ -0,0 +1,60 @@
+import { cookies } from "next/headers";
+import { getArtistData } from "@/lib/artist-data";
+import { getTenantId } from "@/lib/tenant";
+
+// Server-side translation helper for admin functions
+async function getAdminTranslations(locale?: string) {
+ // Get locale from cookie if not provided
+ if (!locale) {
+ const cookieStore = await cookies();
+ locale = cookieStore.get("locale")?.value;
+ }
+
+ if (!locale) {
+ throw new Error("[admin-i18n] Missing locale for admin translations.");
+ }
+
+ const adminMessages = await import(`../../messages/ui/admin/${locale}.json`);
+ return adminMessages.default;
+}
+
+// Helper to get email translations specifically
+export async function getEmailTranslations(locale?: string) {
+ const translations = await getAdminTranslations(locale);
+ return translations.Email;
+}
+
+// Resolve localized site name from UI messages.
+// Fails fast when locale/message keys are missing.
+export async function getLocalizedSiteName(locale?: string): Promise {
+ if (!locale) {
+ const cookieStore = await cookies();
+ locale = cookieStore.get("locale")?.value;
+ }
+
+ if (!locale) {
+ throw new Error(
+ "[admin-i18n] Missing locale for localized site name resolution.",
+ );
+ }
+
+ const uiMessages = await import(`../../messages/ui/${locale}.json`);
+ const siteNameTemplate = uiMessages.default?.Site?.name;
+
+ if (typeof siteNameTemplate !== "string" || siteNameTemplate.length === 0) {
+ throw new Error(
+ `[admin-i18n] Missing messages/ui/${locale}.json -> Site.name`,
+ );
+ }
+
+ const tenantId = await getTenantId();
+ const artistData = await getArtistData(tenantId, locale);
+
+ if (!artistData.name || typeof artistData.name !== "string") {
+ throw new Error(
+ `[admin-i18n] Missing artist name for tenant "${tenantId}" and locale "${locale}".`,
+ );
+ }
+
+ return siteNameTemplate.replace("{artistName}", artistData.name);
+}
diff --git a/src/lib/artist-data-server.ts b/src/lib/artist-data-server.ts
new file mode 100644
index 0000000..fedfdce
--- /dev/null
+++ b/src/lib/artist-data-server.ts
@@ -0,0 +1,88 @@
+import "server-only";
+
+import { promises as fs } from "fs";
+import { ArtistData, ArtistTranslations } from "@/types/admin";
+import { tenantDataPath } from "@/lib/tenant";
+
+// Cache for artist data to avoid repeated file reads — keyed by tenantId
+const artistDataCache = new Map();
+const artistTranslationsCache = new Map();
+
+/**
+ * Read the primary artist data from data/tenants/{tenantId}/artist.json
+ */
+export async function readUserArtistData(
+ tenantId: string,
+): Promise {
+ const cached = artistDataCache.get(tenantId);
+ if (cached) return cached;
+
+ const filePath = tenantDataPath(tenantId, "artist.json");
+ const fileContent = await fs.readFile(filePath, "utf-8");
+ const data: ArtistData = JSON.parse(fileContent);
+ artistDataCache.set(tenantId, data);
+ return data;
+}
+
+/**
+ * Read user-provided translations from data/tenants/{tenantId}/artist-translations.json
+ */
+export async function readUserArtistTranslations(
+ tenantId: string,
+): Promise {
+ const cached = artistTranslationsCache.get(tenantId);
+ if (cached) return cached;
+
+ const filePath = tenantDataPath(tenantId, "artist-translations.json");
+ const fileContent = await fs.readFile(filePath, "utf-8");
+ const data: ArtistTranslations = JSON.parse(fileContent);
+ artistTranslationsCache.set(tenantId, data);
+ return data;
+}
+
+/**
+ * Write artist data to data/tenants/{tenantId}/artist.json
+ */
+export async function writeUserArtistData(
+ tenantId: string,
+ data: Omit,
+): Promise {
+ const userData = await readUserArtistData(tenantId);
+ const updatedData: ArtistData = {
+ ...data,
+ defaultLanguage: userData.defaultLanguage,
+ };
+
+ const filePath = tenantDataPath(tenantId, "artist.json");
+ await fs.writeFile(filePath, JSON.stringify(updatedData, null, 2), "utf-8");
+
+ // Clear cache to force reload
+ artistDataCache.delete(tenantId);
+}
+
+/**
+ * Write user translations to data/tenants/{tenantId}/artist-translations.json
+ */
+export async function writeUserArtistTranslations(
+ tenantId: string,
+ translations: ArtistTranslations,
+): Promise {
+ const filePath = tenantDataPath(tenantId, "artist-translations.json");
+ await fs.writeFile(filePath, JSON.stringify(translations, null, 2), "utf-8");
+
+ // Clear cache to force reload
+ artistTranslationsCache.delete(tenantId);
+}
+
+/**
+ * Clear caches (useful for testing or when data changes)
+ */
+export function clearArtistDataCache(tenantId?: string): void {
+ if (tenantId) {
+ artistDataCache.delete(tenantId);
+ artistTranslationsCache.delete(tenantId);
+ } else {
+ artistDataCache.clear();
+ artistTranslationsCache.clear();
+ }
+}
diff --git a/src/lib/artist-data.ts b/src/lib/artist-data.ts
new file mode 100644
index 0000000..ceb9230
--- /dev/null
+++ b/src/lib/artist-data.ts
@@ -0,0 +1,59 @@
+import "server-only";
+
+import { promises as fs } from "fs";
+import { ArtistData, ArtistTranslations } from "@/types/admin";
+import { tenantDataPath, tenantMessagePath } from "@/lib/tenant";
+
+/**
+ * Get artist data for a specific locale.
+ *
+ * Priority: locale-specific translation → tenant primary language.
+ *
+ * Fails fast on missing/corrupt tenant files — these are not optional.
+ * A missing locale translation is expected (not every tenant has every
+ * locale), so we fall back to the tenant's primary language.
+ */
+export async function getArtistData(
+ tenantId: string,
+ locale: string,
+): Promise<{ name: string; description: string }> {
+ // These reads MUST succeed — tenant data files are required.
+ // Let errors propagate (ENOENT, bad JSON) so callers see them immediately.
+ const [artistRaw, translationsRaw] = await Promise.all([
+ fs.readFile(tenantDataPath(tenantId, "artist.json"), "utf-8"),
+ fs.readFile(
+ tenantDataPath(tenantId, "artist-translations.json"),
+ "utf-8",
+ ),
+ ]);
+
+ const artistData: ArtistData = JSON.parse(artistRaw);
+ const translations: ArtistTranslations = JSON.parse(translationsRaw);
+
+ // Best case: we have a translation for the requested locale
+ if (translations[locale]) {
+ return {
+ name: translations[locale].name,
+ description: translations[locale].description,
+ };
+ }
+
+ // Fall back to the tenant's primary language (always present in artist.json)
+ return {
+ name: artistData.name,
+ description: artistData.description,
+ };
+}
+
+/**
+ * Get static artist translation from messages/artist/{tenantId}/{locale}.json.
+ * Fails fast if the file is missing — caller should handle.
+ */
+export async function getStaticArtistTranslation(
+ tenantId: string,
+ locale: string,
+): Promise<{ name: string; description: string }> {
+ const filePath = tenantMessagePath("artist", tenantId, `${locale}.json`);
+ const raw = await fs.readFile(filePath, "utf-8");
+ return JSON.parse(raw);
+}
diff --git a/src/lib/artworks.ts b/src/lib/artworks.ts
new file mode 100644
index 0000000..1236fd1
--- /dev/null
+++ b/src/lib/artworks.ts
@@ -0,0 +1,180 @@
+import "server-only";
+
+import { promises as fs } from "fs";
+import { CategorySection, Artwork } from "@/types/artwork";
+import { tenantDataPath } from "@/lib/tenant";
+
+// Interface for base artwork data (before translation)
+interface BaseArtwork {
+ id: string;
+ imageUrl: string;
+ category: string;
+ mediumKey: string;
+ dimensions: string;
+ year: number;
+}
+
+interface BaseCategorySection {
+ id: string;
+ artworks: BaseArtwork[];
+}
+
+interface ArtworksBaseData {
+ categorySections: BaseCategorySection[];
+}
+
+/**
+ * Load the base artwork data for a tenant from
+ * `data/tenants/{tenantId}/artworks-base.json`.
+ */
+async function loadBaseArtworks(
+ tenantId: string,
+): Promise {
+ const filePath = tenantDataPath(tenantId, "artworks-base.json");
+ const raw = await fs.readFile(filePath, "utf-8");
+ const data: ArtworksBaseData = JSON.parse(raw);
+ return data.categorySections;
+}
+
+// Utility function to merge base artwork data with translations
+// Note: This function expects a translation function to be passed in
+export async function mergeArtworksWithTranslations(
+ tenantId: string,
+ t: (key: string) => string,
+): Promise {
+ const baseSections = await loadBaseArtworks(tenantId);
+
+ return baseSections.map((section) => ({
+ id: section.id as CategorySection["id"],
+ title: t(`Categories.${section.id}.title`),
+ description: t(`Categories.${section.id}.description`),
+ artworks: section.artworks.map((artwork) => ({
+ id: artwork.id,
+ imageUrl: artwork.imageUrl,
+ category: artwork.category as Artwork["category"],
+ dimensions: artwork.dimensions,
+ year: artwork.year,
+ title: t(`Artworks.${artwork.id}.title`),
+ description: t(`Artworks.${artwork.id}.description`),
+ medium: t(`Mediums.${artwork.mediumKey}`),
+ })),
+ }));
+}
+
+// Get base artwork data (without translations)
+export async function getBaseArtworks(
+ tenantId: string,
+): Promise {
+ return loadBaseArtworks(tenantId);
+}
+
+// Utility function to filter artworks
+function filterArtworks(
+ sections: CategorySection[],
+ category?: string,
+ year?: string,
+ medium?: string,
+ search?: string,
+): CategorySection[] {
+ return sections
+ .map((section) => {
+ // If category is specified and doesn't match, skip this section
+ if (category && section.id !== category) {
+ return null;
+ }
+
+ let filteredArtworks = section.artworks;
+
+ // Filter by year
+ if (year) {
+ filteredArtworks = filteredArtworks.filter(
+ (art) => art.year === parseInt(year),
+ );
+ }
+
+ // Filter by medium
+ if (medium) {
+ filteredArtworks = filteredArtworks.filter((art) =>
+ art.medium?.toLowerCase().includes(medium.toLowerCase()),
+ );
+ }
+
+ // Filter by search term (in title or description)
+ if (search) {
+ const searchLower = search.toLowerCase();
+ filteredArtworks = filteredArtworks.filter(
+ (art) =>
+ art.title.toLowerCase().includes(searchLower) ||
+ art.description.toLowerCase().includes(searchLower),
+ );
+ }
+
+ if (filteredArtworks.length === 0) {
+ return null;
+ }
+
+ return {
+ ...section,
+ artworks: filteredArtworks,
+ };
+ })
+ .filter((section): section is CategorySection => section !== null);
+}
+
+// Utility function to sort artworks
+function sortArtworks(
+ sections: CategorySection[],
+ sortBy?: string,
+ order: "asc" | "desc" = "asc",
+): CategorySection[] {
+ return sections.map((section) => ({
+ ...section,
+ artworks: [...section.artworks].sort((a, b) => {
+ let comparison: number;
+ switch (sortBy) {
+ case "title":
+ comparison = a.title.localeCompare(b.title);
+ break;
+ case "year":
+ comparison = (a.year || 0) - (b.year || 0);
+ break;
+ default:
+ return 0;
+ }
+ return order === "asc" ? comparison : -comparison;
+ }),
+ }));
+}
+
+interface GetArtworksOptions {
+ category?: string;
+ year?: string;
+ medium?: string;
+ search?: string;
+ sortBy?: string;
+ order?: "asc" | "desc";
+}
+
+// Legacy function - now expects pre-translated sections
+export async function getArtworks(
+ sections: CategorySection[],
+ options: GetArtworksOptions = {},
+): Promise {
+ const { category, year, medium, search, sortBy, order = "asc" } = options;
+
+ // Apply filters
+ let filteredSections = filterArtworks(
+ sections,
+ category,
+ year,
+ medium,
+ search,
+ );
+
+ // Apply sorting
+ if (sortBy) {
+ filteredSections = sortArtworks(filteredSections, sortBy, order);
+ }
+
+ return filteredSections;
+}
diff --git a/src/lib/chat-tools.ts b/src/lib/chat-tools.ts
new file mode 100644
index 0000000..e80dc50
--- /dev/null
+++ b/src/lib/chat-tools.ts
@@ -0,0 +1,74 @@
+import { z } from "zod";
+import { ACCENT_COLOR_KEYS, COLOR_SCHEME_KEYS } from "@/types/theme";
+import { LOCALE_KEYS } from "@/lib/locales";
+
+/**
+ * Client-side chat tool definitions.
+ *
+ * These are registered on the server (without `execute`) so the LLM can
+ * invoke them, and handled on the client via the `onToolCall` callback
+ * in `useChat`.
+ *
+ * To add a new client-side tool:
+ * 1. Add its schema + description here.
+ * 2. Spread `clientToolDefs` into `streamText({ tools })` in the API route.
+ * 3. Provide a matching handler via the `clientTools` prop on ``.
+ */
+
+export const setThemeInputSchema = z.object({
+ accent: z
+ .enum(ACCENT_COLOR_KEYS)
+ .optional()
+ .describe("Gallery accent color"),
+ colorScheme: z
+ .enum(COLOR_SCHEME_KEYS)
+ .optional()
+ .describe("Color scheme / appearance preference"),
+});
+
+export const getThemeInputSchema = z.object({});
+
+export const setLanguageInputSchema = z.object({
+ locale: z
+ .enum(LOCALE_KEYS)
+ .describe("Target locale code (e.g. \"en\", \"de\", \"es\")"),
+});
+
+export const getLanguageInputSchema = z.object({});
+
+/** Tool definitions passed to `streamText`. No `execute` → client-side. */
+export const clientToolDefs = {
+ setTheme: {
+ description:
+ "Change the gallery's visual theme (accent color and/or color scheme). " +
+ "Call when the visitor asks to change colors, switch to dark mode, etc.",
+ inputSchema: setThemeInputSchema,
+ },
+ getTheme: {
+ description:
+ "Return the gallery's current visual theme (accent color and resolved color scheme). " +
+ "Call to check the current theme before suggesting or confirming changes.",
+ inputSchema: getThemeInputSchema,
+ },
+ setLanguage: {
+ description:
+ "Switch the gallery's display language. The page will navigate to the " +
+ "new locale and the chat conversation will be preserved. " +
+ "Call when the visitor asks to change language.",
+ inputSchema: setLanguageInputSchema,
+ },
+ getLanguage: {
+ description:
+ "Return the gallery's current display language (locale code and name). " +
+ "Call to check the current language before suggesting or confirming changes.",
+ inputSchema: getLanguageInputSchema,
+ },
+} as const;
+
+/**
+ * Handler for a single client-side tool.
+ * Receives the tool input and returns a short result message for the model.
+ */
+export type ChatToolHandler = (
+ input: Record,
+) => string | Promise;
diff --git a/src/lib/config.ts b/src/lib/config.ts
new file mode 100644
index 0000000..7abdde5
--- /dev/null
+++ b/src/lib/config.ts
@@ -0,0 +1,30 @@
+import "server-only";
+
+import { promises as fs } from "fs";
+import { PersonalMessage } from "@/types/config";
+import { tenantDataPath } from "@/lib/tenant";
+
+/**
+ * Load the personal message for a tenant from
+ * `data/tenants/{tenantId}/personal-message.json`.
+ */
+export async function getPersonalMessage(
+ tenantId: string,
+): Promise {
+ const filePath = tenantDataPath(tenantId, "personal-message.json");
+ const raw = await fs.readFile(filePath, "utf-8");
+ return JSON.parse(raw) as PersonalMessage;
+}
+
+/**
+ * Load the site keywords for a tenant from
+ * `data/tenants/{tenantId}/tags.json`.
+ */
+export async function getSiteKeywords(
+ tenantId: string,
+): Promise {
+ const filePath = tenantDataPath(tenantId, "tags.json");
+ const raw = await fs.readFile(filePath, "utf-8");
+ const data = JSON.parse(raw) as { siteKeywords: string[] };
+ return data.siteKeywords;
+}
diff --git a/src/lib/locales.ts b/src/lib/locales.ts
new file mode 100644
index 0000000..7431375
--- /dev/null
+++ b/src/lib/locales.ts
@@ -0,0 +1,69 @@
+/**
+ * Centralized locale configuration for the application
+ * This ensures consistency between LanguageSwitcher and admin translation management
+ */
+
+interface LocaleConfig {
+ code: string;
+ name: string;
+ flag: string;
+}
+
+export const SUPPORTED_LOCALES: readonly LocaleConfig[] = [
+ { code: "en", name: "English", flag: "🇺🇸" },
+ { code: "de", name: "Deutsch", flag: "🇩🇪" },
+ { code: "es", name: "Español", flag: "🇪🇸" },
+ { code: "ka", name: "ქართული", flag: "🇬🇪" },
+ { code: "ru", name: "Русский", flag: "🇷🇺" },
+ { code: "tr", name: "Türkçe", flag: "🇹🇷" },
+] as const;
+
+/**
+ * Get locale configuration by code
+ */
+export function getLocaleConfig(code: string): LocaleConfig | undefined {
+ return SUPPORTED_LOCALES.find((locale) => locale.code === code);
+}
+
+/**
+ * Get locales for admin translation management (without flags)
+ */
+export function getAdminLocales() {
+ return SUPPORTED_LOCALES.map(({ code, name }) => ({ code, name }));
+}
+
+// ── Derived key tuple (usable with z.enum(), tool schemas, etc.) ───
+
+/** Typed tuple of locale codes — usable with z.enum() and Set(). */
+export const LOCALE_KEYS = SUPPORTED_LOCALES.map((l) => l.code) as [
+ string,
+ ...string[],
+];
+
+// ── Derived look-ups (shared by API routes, components, etc.) ─────
+
+/** Set of supported locale codes for fast validation. */
+export const supportedLocaleCodes: ReadonlySet = new Set(
+ SUPPORTED_LOCALES.map((l) => l.code),
+);
+
+/** Map locale code → human-readable language name (for LLM prompts). */
+export const localeToLanguageName: Readonly> =
+ Object.fromEntries(SUPPORTED_LOCALES.map((l) => [l.code, l.name]));
+
+/**
+ * Map app locale codes to explicit BCP-47 tags for the Web Speech API.
+ * Many speech engines are more reliable with full tags (e.g. "en-US")
+ * than short codes (e.g. "en").
+ */
+export function toSpeechLang(locale: string): string {
+ const map: Record = {
+ en: "en-US",
+ de: "de-DE",
+ es: "es-ES",
+ tr: "tr-TR",
+ ru: "ru-RU",
+ ka: "ka-GE",
+ };
+ return map[locale] ?? "en-US";
+}
diff --git a/src/lib/normalize-host.ts b/src/lib/normalize-host.ts
new file mode 100644
index 0000000..86e372f
--- /dev/null
+++ b/src/lib/normalize-host.ts
@@ -0,0 +1,13 @@
+/**
+ * Normalise a raw Host header value for deterministic lookup:
+ * - lowercase
+ * - strip port
+ * - strip trailing dot (DNS root)
+ *
+ * Extracted to its own module so both the Edge proxy (`proxy.ts`) and
+ * the Node-only tenant resolver (`lib/tenant.ts`) can share the same
+ * implementation without runtime restrictions.
+ */
+export function normalizeHost(raw: string): string {
+ return raw.toLowerCase().replace(/:\d+$/, "").replace(/\.$/, "");
+}
diff --git a/src/lib/openrouter.ts b/src/lib/openrouter.ts
new file mode 100644
index 0000000..766f81e
--- /dev/null
+++ b/src/lib/openrouter.ts
@@ -0,0 +1,14 @@
+import "server-only";
+import { createOpenRouter } from "@openrouter/ai-sdk-provider";
+
+/**
+ * Server-side OpenRouter client.
+ * Reads the API key from the OPENROUTER_API_KEY environment variable.
+ *
+ * Usage (server only):
+ * import { openrouter } from "@/lib/openrouter";
+ * const model = openrouter("openai/gpt-4o-mini");
+ */
+export const openrouter = createOpenRouter({
+ apiKey: process.env.OPENROUTER_API_KEY ?? "",
+});
diff --git a/src/lib/speech-recognition.ts b/src/lib/speech-recognition.ts
new file mode 100644
index 0000000..e121b67
--- /dev/null
+++ b/src/lib/speech-recognition.ts
@@ -0,0 +1,44 @@
+/**
+ * Web Speech API type shims and helpers.
+ *
+ * The Web Speech API isn't in lib.dom.d.ts yet. We define a minimal
+ * interface so TypeScript is happy without pulling in a package.
+ */
+
+// ── Type shims ────────────────────────────────────────────────────
+
+export interface SpeechRecognitionEvent extends Event {
+ // eslint-disable-next-line no-undef
+ results: SpeechRecognitionResultList;
+ resultIndex: number;
+}
+
+export interface SpeechRecognitionErrorEvent extends Event {
+ error: string;
+}
+
+export interface SpeechRecognitionInstance extends EventTarget {
+ lang: string;
+ interimResults: boolean;
+ continuous: boolean;
+ start(): void;
+ stop(): void;
+ abort(): void;
+ onresult: ((event: SpeechRecognitionEvent) => void) | null;
+ onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;
+ onend: (() => void) | null;
+}
+
+// ── Helpers ───────────────────────────────────────────────────────
+
+/** Get the SpeechRecognition constructor if the browser supports it. */
+export function getSpeechRecognition():
+ | (new () => SpeechRecognitionInstance)
+ | null {
+ if (typeof window === "undefined") return null;
+
+ const SR =
+ (window as any).SpeechRecognition ?? // eslint-disable-line @typescript-eslint/no-explicit-any
+ (window as any).webkitSpeechRecognition; // eslint-disable-line @typescript-eslint/no-explicit-any
+ return SR ?? null;
+}
diff --git a/src/lib/tenant.ts b/src/lib/tenant.ts
new file mode 100644
index 0000000..1b2fe8b
--- /dev/null
+++ b/src/lib/tenant.ts
@@ -0,0 +1,151 @@
+import "server-only";
+
+import { promises as fs } from "fs";
+import path from "path";
+import { headers } from "next/headers";
+import { normalizeHost } from "./normalize-host";
+
+// ── Types ────────────────────────────────────────────────────────────────
+
+/** hostname (normalised) → tenantId */
+type TenantsMap = Record;
+
+// ── Tenants map (lazy-loaded, cached) ────────────────────────────────────
+
+let tenantsMapCache: TenantsMap | null = null;
+
+/**
+ * Load and cache the hostname → tenantId mapping from `data/tenants.json`.
+ * Keys are normalised (lowercased, port-stripped) at load time so lookups
+ * are always consistent.
+ */
+async function loadTenantsMap(): Promise {
+ if (tenantsMapCache) return tenantsMapCache;
+
+ const filePath = path.resolve(process.cwd(), "data", "tenants.json");
+ const raw: Record = JSON.parse(
+ await fs.readFile(filePath, "utf-8"),
+ );
+
+ // Normalise keys once at load time
+ const normalised: TenantsMap = {};
+ for (const [host, tenantId] of Object.entries(raw)) {
+ normalised[normalizeHost(host)] = tenantId;
+ }
+
+ tenantsMapCache = normalised;
+ return tenantsMapCache;
+}
+
+// ── Default / fallback tenant ────────────────────────────────────────────
+
+const DEFAULT_TENANT_ID = process.env.DEFAULT_TENANT_ID ?? "nini";
+
+// ── Public API ───────────────────────────────────────────────────────────
+
+/**
+ * Resolve the tenant ID from the incoming `Host` header.
+ *
+ * In development (`NODE_ENV !== "production"`) an unknown host falls back
+ * to `DEFAULT_TENANT_ID` so preview URLs and localhost variants keep working.
+ *
+ * In production an unknown host returns `null` (caller should 404).
+ */
+export async function resolveTenantFromHost(
+ rawHost: string,
+): Promise {
+ const host = normalizeHost(rawHost);
+ const map = await loadTenantsMap();
+ const tenantId = map[host];
+
+ if (tenantId) return tenantId;
+
+ // Dev/staging fallback
+ if (process.env.NODE_ENV !== "production") {
+ return DEFAULT_TENANT_ID;
+ }
+
+ return null;
+}
+
+/**
+ * Get the tenant ID inside a **server component or server action**.
+ * Reads the `x-tenant-id` header that `proxy.ts` injected.
+ *
+ * During static prerendering (e.g. `_not-found`, `_global-error`)
+ * there is no request context, so `headers()` throws an InvariantError.
+ * Only that specific case falls back to `DEFAULT_TENANT_ID`.
+ *
+ * All other failures (bad tenants.json, unresolvable host at runtime)
+ * throw immediately — fail-fast, never silently serve wrong tenant.
+ */
+export async function getTenantId(): Promise {
+ let h: Awaited>;
+ try {
+ h = await headers();
+ } catch {
+ // Static prerendering — no request context. Safe to fall back.
+ return DEFAULT_TENANT_ID;
+ }
+
+ // We have a live request — resolve tenant or fail.
+ const tenantId = h.get("x-tenant-id");
+ if (tenantId) return tenantId;
+
+ // Fallback: resolve from Host directly (defensive)
+ const host = h.get("host") ?? "";
+ const resolved = await resolveTenantFromHost(host);
+ if (resolved) return resolved;
+
+ throw new Error(
+ `[tenant] Unable to resolve tenant for host "${host}". ` +
+ "Check data/tenants.json or proxy.ts configuration.",
+ );
+}
+
+/**
+ * Get the tenant ID inside an **API route handler**.
+ * Resolves from the `Host` header directly — does not rely on
+ * proxy-injected headers (unreliable across edge → node boundary).
+ */
+export async function getTenantIdFromRequest(
+ req: Request,
+): Promise {
+ const rawHost = req.headers.get("host") ?? "";
+ return resolveTenantFromHost(rawHost);
+}
+
+/**
+ * Build the absolute filesystem path to a tenant's data file.
+ * Example: `tenantDataPath("nini", "artist.json")` →
+ * `/app/data/tenants/nini/artist.json`
+ */
+export function tenantDataPath(tenantId: string, ...segments: string[]): string {
+ return path.resolve(process.cwd(), "data", "tenants", tenantId, ...segments);
+}
+
+/**
+ * Build the absolute filesystem path to a tenant's message file.
+ * Example: `tenantMessagePath("artworks", "nini", "en.json")` →
+ * `/app/messages/artworks/nini/en.json`
+ */
+export function tenantMessagePath(
+ category: string,
+ tenantId: string,
+ ...segments: string[]
+): string {
+ return path.resolve(
+ process.cwd(),
+ "messages",
+ category,
+ tenantId,
+ ...segments,
+ );
+}
+
+/**
+ * Clear the cached tenants map. Useful for testing or hot-reload.
+ */
+export function clearTenantsCache(): void {
+ tenantsMapCache = null;
+}
diff --git a/src/lib/wav-encoder.ts b/src/lib/wav-encoder.ts
new file mode 100644
index 0000000..6fc6bd9
--- /dev/null
+++ b/src/lib/wav-encoder.ts
@@ -0,0 +1,124 @@
+/**
+ * Client-side WAV encoding utilities.
+ *
+ * MediaRecorder outputs browser-specific codecs (webm/opus, ogg/opus, etc.).
+ * Most audio LLMs work best with plain WAV, so we decode the browser blob
+ * via AudioContext and re-encode it as 16-bit mono PCM WAV.
+ */
+
+/**
+ * Convert any browser-recorded audio blob (webm, ogg, mp4 …) into a
+ * 16-bit mono WAV blob at the original sample rate.
+ */
+export async function blobToWav(blob: Blob): Promise {
+ const arrayBuffer = await blob.arrayBuffer();
+ const audioCtx = new AudioContext();
+
+ let audioBuffer: AudioBuffer;
+ try {
+ audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
+ } finally {
+ await audioCtx.close();
+ }
+
+ // Down-mix to mono by averaging all channels.
+ const numChannels = audioBuffer.numberOfChannels;
+ const length = audioBuffer.length;
+ const sampleRate = audioBuffer.sampleRate;
+ const mono = new Float32Array(length);
+
+ for (let ch = 0; ch < numChannels; ch++) {
+ const channelData = audioBuffer.getChannelData(ch);
+ for (let i = 0; i < length; i++) {
+ mono[i] += channelData[i] / numChannels;
+ }
+ }
+
+ // Encode as 16-bit PCM WAV.
+ const bytesPerSample = 2;
+ const dataLength = length * bytesPerSample;
+ const buffer = new ArrayBuffer(44 + dataLength);
+ const view = new DataView(buffer);
+
+ // ---- RIFF header ----
+ writeString(view, 0, "RIFF");
+ view.setUint32(4, 36 + dataLength, true);
+ writeString(view, 8, "WAVE");
+
+ // ---- fmt sub-chunk ----
+ writeString(view, 12, "fmt ");
+ view.setUint32(16, 16, true); // sub-chunk size
+ view.setUint16(20, 1, true); // PCM format
+ view.setUint16(22, 1, true); // mono
+ view.setUint32(24, sampleRate, true);
+ view.setUint32(28, sampleRate * bytesPerSample, true); // byte rate
+ view.setUint16(32, bytesPerSample, true); // block align
+ view.setUint16(34, 16, true); // bits per sample
+
+ // ---- data sub-chunk ----
+ writeString(view, 36, "data");
+ view.setUint32(40, dataLength, true);
+
+ // Write PCM samples (clamp to [-1, 1] then scale to int16).
+ let offset = 44;
+ for (let i = 0; i < length; i++) {
+ const sample = Math.max(-1, Math.min(1, mono[i]));
+ view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7fff, true);
+ offset += 2;
+ }
+
+ return new Blob([buffer], { type: "audio/wav" });
+}
+
+/**
+ * Convert a Blob to a raw base64 string (no `data:` prefix).
+ */
+export async function blobToBase64(blob: Blob): Promise {
+ const buffer = await blob.arrayBuffer();
+ const bytes = new Uint8Array(buffer);
+ let binary = "";
+ for (let i = 0; i < bytes.byteLength; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ return btoa(binary);
+}
+
+/**
+ * Check whether a WAV blob produced by `blobToWav` is effectively silent.
+ *
+ * Reads the 16-bit PCM samples directly (no extra AudioContext), computes
+ * the RMS energy in dBFS, and returns `true` if it falls below the
+ * threshold. Default –35 dBFS catches ambient room noise while letting
+ * normal speech through (typically –12 to –20 dBFS).
+ */
+export async function isWavSilent(
+ wavBlob: Blob,
+ thresholdDb = -35,
+): Promise {
+ const buffer = await wavBlob.arrayBuffer();
+ const view = new DataView(buffer);
+
+ const dataOffset = 44; // standard WAV header length
+ const numSamples = (buffer.byteLength - dataOffset) / 2; // 16-bit = 2 bytes
+
+ if (numSamples === 0) return true;
+
+ let sumSquares = 0;
+ for (let i = 0; i < numSamples; i++) {
+ // Normalise int16 → [–1, 1]
+ const sample = view.getInt16(dataOffset + i * 2, true) / 0x8000;
+ sumSquares += sample * sample;
+ }
+
+ const rms = Math.sqrt(sumSquares / numSamples);
+ const dbfs = rms > 0 ? 20 * Math.log10(rms) : -Infinity;
+
+ return dbfs < thresholdDb;
+}
+
+// ── internal helper ─────────────────────────────────────────────
+function writeString(view: DataView, offset: number, str: string) {
+ for (let i = 0; i < str.length; i++) {
+ view.setUint8(offset + i, str.charCodeAt(i));
+ }
+}
diff --git a/src/proxy.ts b/src/proxy.ts
new file mode 100644
index 0000000..2ae47ef
--- /dev/null
+++ b/src/proxy.ts
@@ -0,0 +1,107 @@
+import { NextRequest, NextResponse } from "next/server";
+import createMiddleware from "next-intl/middleware";
+import { routing } from "./i18n/routing";
+import tenantsMap from "../data/tenants.json";
+import { normalizeHost } from "./lib/normalize-host";
+
+// ── next-intl locale middleware (created once at module load) ─────────
+
+const handleI18nRouting = createMiddleware(routing);
+
+/** Pre-normalise the tenants map keys once at module load. */
+const normalisedTenants: Record = Object.fromEntries(
+ Object.entries(tenantsMap).map(([host, id]) => [normalizeHost(host), id]),
+);
+
+const DEFAULT_TENANT_ID = process.env.DEFAULT_TENANT_ID ?? "nini";
+
+// ── Proxy ────────────────────────────────────────────────────────────────
+
+export function proxy(request: NextRequest) {
+ const { pathname } = request.nextUrl;
+
+ // ── Tenant resolution (runs for every matched request) ───────────
+ const rawHost = request.headers.get("host") ?? "";
+ const host = normalizeHost(rawHost);
+ let tenantId = normalisedTenants[host] ?? null;
+
+ if (!tenantId) {
+ if (process.env.NODE_ENV !== "production") {
+ // Dev / staging fallback — prevents "why is localhost broken?" confusion
+ tenantId = DEFAULT_TENANT_ID;
+ } else {
+ // Unknown host in production → 404
+ return new NextResponse("Not Found", { status: 404 });
+ }
+ }
+
+ // Inject x-tenant-id into the request headers for downstream consumption
+ // (server components can read this via headers())
+ const requestHeaders = new Headers(request.headers);
+ requestHeaders.set("x-tenant-id", tenantId);
+
+ // ── Admin route authentication (now under locale structure) ──────
+ const adminPathMatch = pathname.match(/^\/[a-z]{2}\/admin/);
+ if (
+ adminPathMatch &&
+ !pathname.includes("/login") &&
+ pathname !== "/api/admin/config"
+ ) {
+ const cookieToken = request.cookies.get("admin-token")?.value;
+ const authHeader = request.headers.get("authorization");
+ const headerToken = authHeader?.startsWith("Bearer ")
+ ? authHeader.slice(7)
+ : null;
+
+ const token = cookieToken || headerToken;
+
+ if (!token) {
+ // Extract locale from the path for redirect
+ const locale = pathname.split("/")[1];
+ return NextResponse.redirect(
+ new URL(`/${locale}/admin/login`, request.url),
+ );
+ }
+ return NextResponse.next({
+ request: { headers: requestHeaders },
+ });
+ }
+
+ // Skip locale handling for API routes and static assets (anything with a
+ // file extension like .mp3, .woff2, .svg, .ico, etc. that lives in public/).
+ if (
+ pathname.startsWith("/api") ||
+ pathname.startsWith("/_next") ||
+ /\.[\w]+$/.test(pathname)
+ ) {
+ return NextResponse.next({
+ request: { headers: requestHeaders },
+ });
+ }
+
+ // ── Locale routing (delegated to next-intl) ──────────────────────
+ // Handles: accept-language negotiation, locale cookie, URL prefix
+ // detection, and redirects for missing/invalid locale prefixes.
+ const response = handleI18nRouting(request);
+
+ // Forward x-tenant-id to the app via the middleware request header
+ // convention so server components can read it via headers().
+ response.headers.set("x-middleware-request-x-tenant-id", tenantId);
+
+ return response;
+}
+
+export const config = {
+ matcher: [
+ /*
+ * Match all request paths except:
+ * - _next/static (static files)
+ * - _next/image (image optimization files)
+ * - files with extensions (static assets in public/: .ico, .svg, .mp3, .woff2, etc.)
+ *
+ * Note: API routes ARE matched now so they receive the x-tenant-id header,
+ * but locale handling still skips them (handled inside the function).
+ */
+ "/((?!_next/static|_next/image)(?!.*\\.\\w+$).*)",
+ ],
+};
diff --git a/src/types/admin.ts b/src/types/admin.ts
new file mode 100644
index 0000000..9b598b9
--- /dev/null
+++ b/src/types/admin.ts
@@ -0,0 +1,48 @@
+export interface OTPSession {
+ email: string;
+ otp: string;
+ expiresAt: number;
+ attempts: number;
+}
+
+export interface AdminSession {
+ email: string;
+ issuedAt: number;
+ expiresAt: number;
+}
+
+export interface OTPRequest {
+ email: string;
+}
+
+export interface OTPVerification {
+ email: string;
+ otp: string;
+}
+
+export interface PersonalMessageUpdate {
+ enabled: boolean;
+ recipient: string;
+ message: string;
+ dismissible: boolean;
+}
+
+export interface ArtistData {
+ name: string;
+ description: string;
+ defaultLanguage: string;
+}
+
+export interface ArtistTranslations {
+ [locale: string]: {
+ name: string;
+ description: string;
+ };
+}
+
+export interface ArtistProfileWithTranslations {
+ name: string;
+ description: string;
+ defaultLanguage: string;
+ translations: ArtistTranslations;
+}
diff --git a/src/types/artwork.ts b/src/types/artwork.ts
new file mode 100644
index 0000000..cee8134
--- /dev/null
+++ b/src/types/artwork.ts
@@ -0,0 +1,19 @@
+type ArtCategory = "origami" | "crochet" | "paintings" | "fingernails";
+
+export interface Artwork {
+ id: string;
+ title: string;
+ description: string;
+ imageUrl: string;
+ category: ArtCategory;
+ dimensions?: string;
+ medium?: string;
+ year?: number;
+}
+
+export interface CategorySection {
+ id: ArtCategory;
+ title: string;
+ description: string;
+ artworks: Artwork[];
+}
diff --git a/src/types/config.ts b/src/types/config.ts
new file mode 100644
index 0000000..7e11862
--- /dev/null
+++ b/src/types/config.ts
@@ -0,0 +1,7 @@
+export interface PersonalMessage {
+ enabled: boolean;
+ recipient: string;
+ message: string;
+ dismissible: boolean;
+ ariaLabel: string;
+}
diff --git a/src/types/theme.ts b/src/types/theme.ts
new file mode 100644
index 0000000..947f74b
--- /dev/null
+++ b/src/types/theme.ts
@@ -0,0 +1,98 @@
+export type AccentColor = "pink" | "orange" | "green";
+export type ColorScheme = "light" | "dark" | "system";
+
+export interface ThemeConfig {
+ accent: AccentColor;
+ colorScheme: ColorScheme;
+}
+
+export interface ThemeContextType {
+ theme: ThemeConfig;
+ setAccentColor: (color: AccentColor) => void;
+ setColorScheme: (scheme: ColorScheme) => void;
+ resolvedColorScheme: "light" | "dark";
+ systemPrefersDark: boolean;
+}
+
+export const ACCENT_COLORS: Record = {
+ pink: {
+ preview: "#ec4899", // pink-500
+ },
+ orange: {
+ preview: "#f97316", // orange-500
+ },
+ green: {
+ preview: "#84cc16", // lime-500
+ },
+};
+
+export const COLOR_SCHEMES: Record = {
+ light: {
+ icon: "☀️",
+ },
+ dark: {
+ icon: "🌙",
+ },
+ system: {
+ icon: "💻",
+ },
+};
+
+// ── Derived key tuples (single source of truth for Zod, Sets, prompts) ──
+
+/** Typed tuple of accent color keys — usable with z.enum() and Set(). */
+export const ACCENT_COLOR_KEYS = Object.keys(ACCENT_COLORS) as [
+ AccentColor,
+ ...AccentColor[],
+];
+
+/** Typed tuple of color scheme keys — usable with z.enum() and Set(). */
+export const COLOR_SCHEME_KEYS = Object.keys(COLOR_SCHEMES) as [
+ ColorScheme,
+ ...ColorScheme[],
+];
+
+/** The only two values a resolved (non-"system") color scheme can have. */
+export const RESOLVED_COLOR_SCHEMES = ["light", "dark"] as const;
+export type ResolvedColorScheme = (typeof RESOLVED_COLOR_SCHEMES)[number];
+
+// ── Client-supplied theme validation ──────────────────────────────
+
+const VALID_ACCENTS = new Set(ACCENT_COLOR_KEYS);
+const VALID_COLOR_SCHEMES = new Set(COLOR_SCHEME_KEYS);
+const VALID_RESOLVED = new Set(RESOLVED_COLOR_SCHEMES);
+
+/** Shape of a validated client-supplied theme (string values, not typed enums). */
+export interface ClientTheme {
+ accent: string;
+ colorScheme: string;
+ resolvedColorScheme: string;
+}
+
+/** Validate and sanitise a client-supplied theme, falling back to safe defaults. */
+export function parseClientTheme(raw: unknown): ClientTheme {
+ const defaults: ClientTheme = {
+ accent: ACCENT_COLOR_KEYS[0],
+ colorScheme: COLOR_SCHEME_KEYS[0],
+ resolvedColorScheme: RESOLVED_COLOR_SCHEMES[0],
+ };
+ if (!raw || typeof raw !== "object") return defaults;
+
+ const obj = raw as Record;
+ return {
+ accent:
+ typeof obj.accent === "string" && VALID_ACCENTS.has(obj.accent)
+ ? obj.accent
+ : defaults.accent,
+ colorScheme:
+ typeof obj.colorScheme === "string" &&
+ VALID_COLOR_SCHEMES.has(obj.colorScheme)
+ ? obj.colorScheme
+ : defaults.colorScheme,
+ resolvedColorScheme:
+ typeof obj.resolvedColorScheme === "string" &&
+ VALID_RESOLVED.has(obj.resolvedColorScheme)
+ ? obj.resolvedColorScheme
+ : defaults.resolvedColorScheme,
+ };
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 109807b..4a9edeb 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -11,6 +11,13 @@ export default {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
+ accent: {
+ 50: "var(--accent-50)",
+ 100: "var(--accent-100)",
+ 200: "var(--accent-200)",
+ 500: "var(--accent-500)",
+ 600: "var(--accent-600)",
+ },
},
},
},
diff --git a/tsconfig.json b/tsconfig.json
index c133409..2365ff9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,8 +11,10 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "preserve",
+ "jsx": "react-jsx",
"incremental": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
"plugins": [
{
"name": "next"
@@ -22,6 +24,12 @@
"@/*": ["./src/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
+ ],
"exclude": ["node_modules"]
}