diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index ca0ca5b..1af63e5 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -138,3 +138,119 @@ jobs:
pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
pip install --quiet --no-cache-dir fastapi uvicorn pydantic pytest pytest-asyncio
python -m pytest tests/bqas/ -v --tb=short || true
+
+ # ========================================
+ # Build & Deploy auf Hetzner (nur main, kein PR)
+ # ========================================
+
+ deploy-hetzner:
+ runs-on: docker
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+ needs:
+ - test-go-consent
+ container: docker:27-cli
+ steps:
+ - name: Deploy
+ run: |
+ set -euo pipefail
+ DEPLOY_DIR="/opt/breakpilot-core"
+ COMPOSE_FILES="-f docker-compose.yml -f docker-compose.hetzner.yml"
+ COMMIT_SHA="${GITHUB_SHA:-unknown}"
+ SHORT_SHA="${COMMIT_SHA:0:8}"
+ REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
+
+ # Services die deployed werden
+ SERVICES="postgres valkey qdrant minio ollama mailpit embedding-service rag-service backend-core consent-service health-aggregator"
+
+ echo "=== BreakPilot Core Deploy ==="
+ echo "Commit: ${SHORT_SHA}"
+ echo "Deploy Dir: ${DEPLOY_DIR}"
+ echo "Services: ${SERVICES}"
+ echo ""
+
+ # 1. Repo auf dem Host erstellen/aktualisieren via Helper-Container
+ echo "=== Updating code on host ==="
+ docker run --rm \
+ -v "${DEPLOY_DIR}:${DEPLOY_DIR}" \
+ --entrypoint sh \
+ alpine/git:latest \
+ -c "
+ if [ ! -d '${DEPLOY_DIR}/.git' ]; then
+ echo 'Erstmaliges Klonen nach ${DEPLOY_DIR}...'
+ git clone '${REPO_URL}' '${DEPLOY_DIR}'
+ else
+ cd '${DEPLOY_DIR}'
+ git fetch origin main
+ git reset --hard origin/main
+ fi
+ "
+ echo "Code aktualisiert auf ${SHORT_SHA}"
+
+ # 2. .env sicherstellen
+ docker run --rm -v "${DEPLOY_DIR}:${DEPLOY_DIR}" alpine \
+ sh -c "
+ if [ ! -f '${DEPLOY_DIR}/.env' ]; then
+ echo 'WARNUNG: ${DEPLOY_DIR}/.env fehlt!'
+ echo 'Erstelle .env aus .env.example mit Defaults...'
+ if [ -f '${DEPLOY_DIR}/.env.example' ]; then
+ cp '${DEPLOY_DIR}/.env.example' '${DEPLOY_DIR}/.env'
+ echo '.env aus .env.example erstellt'
+ else
+ echo 'Kein .env.example gefunden — Services starten mit Defaults'
+ fi
+ else
+ echo '.env vorhanden'
+ fi
+ "
+
+ # 3. Shared Network erstellen (falls noch nicht vorhanden)
+ docker network create breakpilot-network 2>/dev/null || true
+
+ # 4. Build + Deploy via Helper-Container
+ echo ""
+ echo "=== Building + Deploying ==="
+ docker run --rm \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v "${DEPLOY_DIR}:${DEPLOY_DIR}" \
+ -w "${DEPLOY_DIR}" \
+ docker:27-cli \
+ sh -c "
+ set -e
+ COMPOSE_FILES='-f docker-compose.yml -f docker-compose.hetzner.yml'
+
+ echo '=== Building Docker Images ==='
+ docker compose \${COMPOSE_FILES} build --parallel \
+ backend-core consent-service rag-service embedding-service health-aggregator
+
+ echo ''
+ echo '=== Starting infrastructure ==='
+ docker compose \${COMPOSE_FILES} up -d postgres valkey qdrant minio mailpit
+
+ echo 'Warte auf DB + Cache...'
+ sleep 10
+
+ echo ''
+ echo '=== Starting Ollama + pulling bge-m3 ==='
+ docker compose \${COMPOSE_FILES} up -d ollama
+ sleep 5
+
+ # bge-m3 Modell pullen (nur beim ersten Mal ~670MB)
+ echo 'Pulling bge-m3 model (falls noch nicht vorhanden)...'
+ docker exec bp-core-ollama ollama pull bge-m3 2>&1 || echo 'WARNUNG: bge-m3 pull fehlgeschlagen (wird spaeter nachgeholt)'
+
+ echo ''
+ echo '=== Starting application services ==='
+ docker compose \${COMPOSE_FILES} up -d \
+ embedding-service rag-service backend-core consent-service health-aggregator
+
+ echo ''
+ echo '=== Health Checks ==='
+ sleep 15
+ for svc in bp-core-postgres bp-core-valkey bp-core-qdrant bp-core-ollama bp-core-embedding-service bp-core-rag-service bp-core-backend bp-core-consent-service bp-core-health; do
+ STATUS=\$(docker inspect --format='{{.State.Status}}' \"\${svc}\" 2>/dev/null || echo 'not found')
+ echo \"\${svc}: \${STATUS}\"
+ done
+ "
+
+ echo ""
+ echo "=== Deploy abgeschlossen: ${SHORT_SHA} ==="
diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml
index 85d27b2..32cc969 100644
--- a/docker-compose.coolify.yml
+++ b/docker-compose.coolify.yml
@@ -15,7 +15,6 @@ networks:
volumes:
valkey_data:
embedding_models:
- paddleocr_models:
services:
@@ -142,35 +141,6 @@ services:
networks:
- breakpilot-network
- # =========================================================
- # OCR SERVICE (PaddleOCR PP-OCRv5 Latin)
- # =========================================================
- paddleocr-service:
- build:
- context: ./paddleocr-service
- dockerfile: Dockerfile
- container_name: bp-core-paddleocr
- ports:
- - "8095:8095"
- environment:
- PADDLEOCR_API_KEY: ${PADDLEOCR_API_KEY:-}
- FLAGS_use_mkldnn: "0"
- volumes:
- - paddleocr_models:/root/.paddleocr
- deploy:
- resources:
- limits:
- memory: 4G
- healthcheck:
- test: ["CMD", "curl", "-f", "http://127.0.0.1:8095/health"]
- interval: 30s
- timeout: 10s
- start_period: 300s
- retries: 5
- restart: unless-stopped
- networks:
- - breakpilot-network
-
# =========================================================
# HEALTH AGGREGATOR
# =========================================================
@@ -183,7 +153,7 @@ services:
- "8099"
environment:
PORT: 8099
- CHECK_SERVICES: "valkey:6379,consent-service:8081,rag-service:8097,embedding-service:8087,paddleocr-service:8095"
+ CHECK_SERVICES: "valkey:6379,consent-service:8081,rag-service:8097,embedding-service:8087"
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8099/health"]
interval: 30s
diff --git a/docker-compose.hetzner.yml b/docker-compose.hetzner.yml
new file mode 100644
index 0000000..91b21b6
--- /dev/null
+++ b/docker-compose.hetzner.yml
@@ -0,0 +1,175 @@
+# =========================================================
+# BreakPilot Core — Hetzner Override (x86_64)
+# =========================================================
+# Verwendung:
+# docker compose -f docker-compose.yml -f docker-compose.hetzner.yml up -d \
+# postgres valkey qdrant ollama embedding-service rag-service \
+# backend-core consent-service health-aggregator
+#
+# Aenderungen gegenueber Basis (docker-compose.yml):
+# - platform: linux/amd64 (statt arm64)
+# - Ollama Container fuer CPU-Embeddings (bge-m3)
+# - Mailpit ersetzt durch Dummy (kein Mail-Dev-Server noetig)
+# - Vault, Nginx, Gitea etc. deaktiviert via Profile
+# - Netzwerk: auto-create (nicht external)
+# =========================================================
+
+networks:
+ breakpilot-network:
+ external: true
+ name: breakpilot-network
+
+services:
+
+ # =========================================================
+ # NEUE SERVICES
+ # =========================================================
+
+ # Ollama fuer Embeddings (CPU-only, bge-m3)
+ ollama:
+ image: ollama/ollama:latest
+ container_name: bp-core-ollama
+ platform: linux/amd64
+ volumes:
+ - ollama_models:/root/.ollama
+ healthcheck:
+ test: ["CMD-SHELL", "curl -sf http://127.0.0.1:11434/api/tags || exit 1"]
+ interval: 15s
+ timeout: 10s
+ retries: 5
+ start_period: 30s
+ restart: unless-stopped
+ networks:
+ - breakpilot-network
+
+ # =========================================================
+ # PLATFORM OVERRIDES (arm64 → amd64)
+ # =========================================================
+
+ backend-core:
+ platform: linux/amd64
+ build:
+ context: ./backend-core
+ dockerfile: Dockerfile
+ args:
+ TARGETARCH: amd64
+ ports:
+ - "8000:8000"
+ environment:
+ DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-breakpilot}:${POSTGRES_PASSWORD:-breakpilot123}@postgres:5432/${POSTGRES_DB:-breakpilot_db}?options=-csearch_path%3Dcore,public
+ JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-in-production}
+ ENVIRONMENT: ${ENVIRONMENT:-production}
+ VALKEY_URL: redis://valkey:6379/0
+ SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24}
+ CONSENT_SERVICE_URL: http://consent-service:8081
+ USE_VAULT_SECRETS: "false"
+ SMTP_HOST: ${SMTP_HOST:-smtp.example.com}
+ SMTP_PORT: ${SMTP_PORT:-587}
+ SMTP_USERNAME: ${SMTP_USERNAME:-}
+ SMTP_PASSWORD: ${SMTP_PASSWORD:-}
+ SMTP_FROM_NAME: ${SMTP_FROM_NAME:-BreakPilot}
+ SMTP_FROM_ADDR: ${SMTP_FROM_ADDR:-noreply@breakpilot.app}
+
+ consent-service:
+ platform: linux/amd64
+ environment:
+ DATABASE_URL: postgres://${POSTGRES_USER:-breakpilot}:${POSTGRES_PASSWORD:-breakpilot123}@postgres:5432/${POSTGRES_DB:-breakpilot_db}
+ JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-in-production}
+ JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:-your-refresh-secret}
+ PORT: 8081
+ ENVIRONMENT: ${ENVIRONMENT:-production}
+ ALLOWED_ORIGINS: "*"
+ VALKEY_URL: redis://valkey:6379/0
+ SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-24}
+ SMTP_HOST: ${SMTP_HOST:-smtp.example.com}
+ SMTP_PORT: ${SMTP_PORT:-587}
+ SMTP_USERNAME: ${SMTP_USERNAME:-}
+ SMTP_PASSWORD: ${SMTP_PASSWORD:-}
+ SMTP_FROM_NAME: ${SMTP_FROM_NAME:-BreakPilot}
+ SMTP_FROM_ADDR: ${SMTP_FROM_ADDR:-noreply@breakpilot.app}
+ FRONTEND_URL: ${FRONTEND_URL:-https://admin-dev.breakpilot.ai}
+
+ billing-service:
+ platform: linux/amd64
+
+ rag-service:
+ platform: linux/amd64
+ ports:
+ - "8097:8097"
+ environment:
+ PORT: 8097
+ QDRANT_URL: http://qdrant:6333
+ MINIO_ENDPOINT: nbg1.your-objectstorage.com
+ MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-T18RGFVXXG2ZHQ5404TP}
+ MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-KOUU4WO6wh07cQjNgh0IZHkeKQrVfBz6hnIGpNss}
+ MINIO_BUCKET: ${MINIO_BUCKET:-breakpilot-rag}
+ MINIO_SECURE: "true"
+ EMBEDDING_SERVICE_URL: http://embedding-service:8087
+ OLLAMA_URL: http://ollama:11434
+ OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-bge-m3}
+ JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-in-production}
+ ENVIRONMENT: ${ENVIRONMENT:-production}
+
+ embedding-service:
+ platform: linux/amd64
+ ports:
+ - "8087:8087"
+
+ health-aggregator:
+ platform: linux/amd64
+ environment:
+ PORT: 8099
+ CHECK_SERVICES: "postgres:5432,valkey:6379,qdrant:6333,backend-core:8000,rag-service:8097,embedding-service:8087"
+
+ # =========================================================
+ # DUMMY-ERSATZ FUER ABHAENGIGKEITEN
+ # =========================================================
+ # backend-core + consent-service haengen von mailpit ab
+ # (depends_on merged bei compose override, kann nicht entfernt werden)
+ # → Mailpit durch leichtgewichtigen Dummy ersetzen
+
+ mailpit:
+ image: alpine:3.19
+ entrypoint: ["sh", "-c", "echo 'Mailpit dummy on Hetzner' && tail -f /dev/null"]
+ volumes: []
+ ports: []
+ environment: {}
+
+ # Qdrant: RocksDB braucht mehr open files
+ qdrant:
+ ulimits:
+ nofile:
+ soft: 65536
+ hard: 65536
+
+ # minio: rag-service haengt davon ab (depends_on)
+ # Lokal laufen lassen, aber rag-service nutzt externe Hetzner Object Storage
+ # minio bleibt unveraendert (klein, ~50MB RAM)
+
+ # =========================================================
+ # DEAKTIVIERTE SERVICES (via profiles)
+ # =========================================================
+
+ nginx:
+ profiles: ["disabled"]
+ vault:
+ profiles: ["disabled"]
+ vault-init:
+ profiles: ["disabled"]
+ vault-agent:
+ profiles: ["disabled"]
+ gitea:
+ profiles: ["disabled"]
+ gitea-runner:
+ profiles: ["disabled"]
+ night-scheduler:
+ profiles: ["disabled"]
+ admin-core:
+ profiles: ["disabled"]
+ pitch-deck:
+ profiles: ["disabled"]
+ levis-holzbau:
+ profiles: ["disabled"]
+
+volumes:
+ ollama_models:
diff --git a/docker-compose.yml b/docker-compose.yml
index 4b0a5b0..32e4866 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -347,11 +347,11 @@ services:
environment:
PORT: 8097
QDRANT_URL: http://qdrant:6333
- MINIO_ENDPOINT: minio:9000
- MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:-breakpilot}
- MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD:-breakpilot123}
+ MINIO_ENDPOINT: nbg1.your-objectstorage.com
+ MINIO_ACCESS_KEY: T18RGFVXXG2ZHQ5404TP
+ MINIO_SECRET_KEY: KOUU4WO6wh07cQjNgh0IZHkeKQrVfBz6hnIGpNss
MINIO_BUCKET: ${MINIO_BUCKET:-breakpilot-rag}
- MINIO_SECURE: "false"
+ MINIO_SECURE: "true"
EMBEDDING_SERVICE_URL: http://embedding-service:8087
OLLAMA_URL: ${OLLAMA_URL:-http://host.docker.internal:11434}
OLLAMA_EMBED_MODEL: ${OLLAMA_EMBED_MODEL:-bge-m3}
@@ -843,3 +843,20 @@ services:
restart: unless-stopped
networks:
- breakpilot-network
+
+ # =========================================================
+ # LEVIS HOLZBAU - Kinder-Holzwerk-Website
+ # =========================================================
+ levis-holzbau:
+ build:
+ context: ./levis-holzbau
+ dockerfile: Dockerfile
+ container_name: bp-core-levis-holzbau
+ platform: linux/arm64
+ ports:
+ - "3013:3000"
+ environment:
+ NODE_ENV: production
+ restart: unless-stopped
+ networks:
+ - breakpilot-network
diff --git a/levis-holzbau/.dockerignore b/levis-holzbau/.dockerignore
new file mode 100644
index 0000000..79a303d
--- /dev/null
+++ b/levis-holzbau/.dockerignore
@@ -0,0 +1,5 @@
+node_modules
+.next
+.git
+Dockerfile
+.dockerignore
diff --git a/levis-holzbau/Dockerfile b/levis-holzbau/Dockerfile
new file mode 100644
index 0000000..6ffb4b2
--- /dev/null
+++ b/levis-holzbau/Dockerfile
@@ -0,0 +1,27 @@
+FROM node:20-alpine AS base
+
+FROM base AS deps
+WORKDIR /app
+COPY package.json package-lock.json* ./
+RUN npm ci
+
+FROM base AS builder
+WORKDIR /app
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+RUN mkdir -p public
+RUN npm run build
+
+FROM base AS runner
+WORKDIR /app
+ENV NODE_ENV=production
+RUN addgroup --system --gid 1001 nodejs
+RUN adduser --system --uid 1001 nextjs
+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
+USER nextjs
+EXPOSE 3000
+ENV PORT=3000
+ENV HOSTNAME="0.0.0.0"
+CMD ["node", "server.js"]
diff --git a/levis-holzbau/app/globals.css b/levis-holzbau/app/globals.css
new file mode 100644
index 0000000..8942b39
--- /dev/null
+++ b/levis-holzbau/app/globals.css
@@ -0,0 +1,25 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@500;600;700&family=Nunito:wght@400;600;700&display=swap');
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ font-family: 'Nunito', sans-serif;
+ background-color: #FDF8F0;
+ color: #2C2C2C;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-family: 'Quicksand', sans-serif;
+}
+
+@layer utilities {
+ .text-balance {
+ text-wrap: balance;
+ }
+}
diff --git a/levis-holzbau/app/layout.tsx b/levis-holzbau/app/layout.tsx
new file mode 100644
index 0000000..6049bf2
--- /dev/null
+++ b/levis-holzbau/app/layout.tsx
@@ -0,0 +1,21 @@
+import type { Metadata } from 'next'
+import './globals.css'
+import { Navbar } from '@/components/Navbar'
+import { Footer } from '@/components/Footer'
+
+export const metadata: Metadata = {
+ title: 'LEVIS Holzbau — Kinder-Holzwerkstatt',
+ description: 'Lerne Holzfiguren schnitzen und kleine Holzprojekte bauen! Kindgerechte Anleitungen fuer junge Holzwerker.',
+}
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/levis-holzbau/app/page.tsx b/levis-holzbau/app/page.tsx
new file mode 100644
index 0000000..017fe7a
--- /dev/null
+++ b/levis-holzbau/app/page.tsx
@@ -0,0 +1,71 @@
+'use client'
+
+import { motion } from 'framer-motion'
+import { Hammer, TreePine, ShieldCheck } from 'lucide-react'
+import { HeroSection } from '@/components/HeroSection'
+import { ProjectCard } from '@/components/ProjectCard'
+import { projects } from '@/lib/projects'
+
+const features = [
+ {
+ icon: Hammer,
+ title: 'Schnitzen',
+ description: 'Lerne mit Schnitzmesser und Holz umzugehen und forme eigene Figuren.',
+ color: 'bg-primary/10 text-primary',
+ },
+ {
+ icon: TreePine,
+ title: 'Bauen',
+ description: 'Saege, leime und nagle — baue nuetzliche Dinge aus Holz!',
+ color: 'bg-secondary/10 text-secondary',
+ },
+ {
+ icon: ShieldCheck,
+ title: 'Sicherheit',
+ description: 'Jedes Projekt zeigt dir, wie du sicher mit Werkzeug arbeitest.',
+ color: 'bg-accent/10 text-accent',
+ },
+]
+
+export default function HomePage() {
+ const featured = projects.slice(0, 4)
+
+ return (
+ <>
+
+
+ {/* Features */}
+
+
+ {features.map((f, i) => (
+
+
+
+
+ {f.title}
+ {f.description}
+
+ ))}
+
+
+
+ {/* Popular Projects */}
+
+
+ Beliebte Projekte
+
+
+ {featured.map((p) => (
+
+ ))}
+
+
+ >
+ )
+}
diff --git a/levis-holzbau/app/projekte/[slug]/page.tsx b/levis-holzbau/app/projekte/[slug]/page.tsx
new file mode 100644
index 0000000..be6f01d
--- /dev/null
+++ b/levis-holzbau/app/projekte/[slug]/page.tsx
@@ -0,0 +1,120 @@
+import { notFound } from 'next/navigation'
+import Link from 'next/link'
+import { ArrowLeft, Clock, Wrench, Package } from 'lucide-react'
+import { projects, getProject, getRelatedProjects } from '@/lib/projects'
+import { DifficultyBadge } from '@/components/DifficultyBadge'
+import { AgeBadge } from '@/components/AgeBadge'
+import { StepCard } from '@/components/StepCard'
+import { SafetyTip } from '@/components/SafetyTip'
+import { ToolIcon } from '@/components/ToolIcon'
+import { ProjectIllustration } from '@/components/ProjectIllustration'
+import { ProjectCard } from '@/components/ProjectCard'
+
+export function generateStaticParams() {
+ return projects.map((p) => ({ slug: p.slug }))
+}
+
+export default async function ProjectPage({ params }: { params: Promise<{ slug: string }> }) {
+ const { slug } = await params
+ const project = getProject(slug)
+ if (!project) notFound()
+
+ const related = getRelatedProjects(slug)
+
+ return (
+
+ {/* Back */}
+
+
Alle Projekte
+
+
+ {/* Hero */}
+
+
+
+
+
+
+
+ {project.duration}
+
+
+
{project.name}
+
{project.description}
+
+
+
+ {/* Tools & Materials */}
+
+
+
+ Werkzeuge
+
+
+ {project.tools.map((t) => (
+
+
+ {t}
+
+ ))}
+
+
+
+
+ Material
+
+
+ {project.materials.map((m) => (
+
+
+ {m}
+
+ ))}
+
+
+
+
+ {/* Safety */}
+
+
Sicherheitshinweise
+ {project.safetyTips.map((tip) => (
+ {tip}
+ ))}
+
+
+ {/* Steps */}
+
+
Schritt fuer Schritt
+
+ {project.steps.map((step, i) => (
+
+ ))}
+
+
+
+ {/* Skills */}
+
+
Was du lernst
+
+ {project.skills.map((s) => (
+
+ {s}
+
+ ))}
+
+
+
+ {/* Related */}
+
+
Aehnliche Projekte
+
+ {related.map((p) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/levis-holzbau/app/projekte/page.tsx b/levis-holzbau/app/projekte/page.tsx
new file mode 100644
index 0000000..843e2ef
--- /dev/null
+++ b/levis-holzbau/app/projekte/page.tsx
@@ -0,0 +1,59 @@
+'use client'
+
+import { useState } from 'react'
+import { motion } from 'framer-motion'
+import { ProjectCard } from '@/components/ProjectCard'
+import { projects } from '@/lib/projects'
+
+const filters = [
+ { label: 'Alle', value: 0 },
+ { label: 'Anfaenger', value: 1 },
+ { label: 'Fortgeschritten', value: 2 },
+ { label: 'Profi', value: 3 },
+]
+
+export default function ProjektePage() {
+ const [filter, setFilter] = useState(0)
+ const filtered = filter === 0 ? projects : projects.filter((p) => p.difficulty === filter)
+
+ return (
+
+
+ Alle Projekte
+ Waehle ein Projekt und leg los!
+
+
+ {/* Filter */}
+
+ {filters.map((f) => (
+ setFilter(f.value as 0 | 1 | 2 | 3)}
+ className={`px-4 py-2 rounded-xl font-semibold text-sm transition-colors ${
+ filter === f.value
+ ? 'bg-primary text-white'
+ : 'bg-white text-dark/60 hover:bg-primary/5'
+ }`}
+ >
+ {f.label}
+
+ ))}
+
+
+ {/* Grid */}
+
+ {filtered.map((p) => (
+
+ ))}
+
+
+ {filtered.length === 0 && (
+
Keine Projekte in dieser Kategorie.
+ )}
+
+ )
+}
diff --git a/levis-holzbau/app/sicherheit/page.tsx b/levis-holzbau/app/sicherheit/page.tsx
new file mode 100644
index 0000000..0f9bc01
--- /dev/null
+++ b/levis-holzbau/app/sicherheit/page.tsx
@@ -0,0 +1,101 @@
+'use client'
+
+import { motion } from 'framer-motion'
+import { ShieldCheck, Eye, Hand, Scissors, AlertTriangle, Users } from 'lucide-react'
+import { SafetyTip } from '@/components/SafetyTip'
+
+const rules = [
+ { icon: Users, title: 'Immer mit Erwachsenen', text: 'Bei Saegen, Bohren und Schnitzen muss immer ein Erwachsener dabei sein.' },
+ { icon: Hand, title: 'Vom Koerper weg', text: 'Schnitze, saege und schneide immer vom Koerper weg. So kannst du dich nicht verletzen.' },
+ { icon: Eye, title: 'Schutzbrille tragen', text: 'Beim Saegen und Schleifen fliegen Spaene — eine Schutzbrille schuetzt deine Augen.' },
+ { icon: Scissors, title: 'Werkzeug richtig halten', text: 'Greife Werkzeuge immer am Griff. Trage Messer und Saegen mit der Spitze nach unten.' },
+ { icon: AlertTriangle, title: 'Aufgeraeumter Arbeitsplatz', text: 'Raeume Werkzeug nach dem Benutzen weg. Ein ordentlicher Platz ist ein sicherer Platz!' },
+ { icon: ShieldCheck, title: 'Scharfes Werkzeug', text: 'Klingt komisch, aber: Scharfe Messer sind sicherer als stumpfe, weil du weniger Kraft brauchst.' },
+]
+
+const toolGuides = [
+ { name: 'Schnitzmesser', age: 'Ab 6 Jahren (mit Hilfe)', tips: ['Immer vom Koerper weg schnitzen', 'Nach dem Benutzen zuklappen', 'Weiches Holz (Linde) verwenden'] },
+ { name: 'Handsaege', age: 'Ab 7 Jahren (mit Hilfe)', tips: ['Holz immer fest einspannen', 'Langsam und gleichmaessig saegen', 'Nicht auf die Klinge druecken'] },
+ { name: 'Hammer', age: 'Ab 5 Jahren', tips: ['Leichten Kinderhammer verwenden', 'Naegel mit Zange halten, nie mit Fingern', 'Auf stabile Unterlage achten'] },
+ { name: 'Schleifpapier', age: 'Ab 5 Jahren', tips: ['Immer in eine Richtung schleifen', 'Staub nicht einatmen', 'Erst grob, dann fein'] },
+ { name: 'Holzleim', age: 'Ab 5 Jahren', tips: ['Nicht giftig, aber nicht essen', 'Duenn auftragen reicht', 'Mindestens 1 Stunde trocknen lassen'] },
+]
+
+export default function SicherheitPage() {
+ return (
+
+
+
+
+
+ Sicherheit geht vor!
+
+ Holzarbeiten macht riesig Spass — aber nur, wenn du sicher arbeitest.
+ Hier findest du die wichtigsten Regeln.
+
+
+
+ {/* Rules Grid */}
+
+ Die goldenen Regeln
+
+ {rules.map((r, i) => (
+
+
+
+
+
+
+ ))}
+
+
+
+ {/* Tool Guides */}
+
+ Werkzeug-Guide
+
+ {toolGuides.map((tool) => (
+
+
+
{tool.name}
+ {tool.age}
+
+
+ {tool.tips.map((tip) => (
+
+
+ {tip}
+
+ ))}
+
+
+ ))}
+
+
+
+ {/* Parents */}
+
+ Hinweise fuer Eltern
+
+ Beaufsichtigen Sie Ihr Kind bei allen Projekten — besonders beim Umgang mit Schneidwerkzeugen.
+ Stellen Sie altersgerechtes Werkzeug bereit. Kinderschnitzmesser haben abgerundete Spitzen.
+ Richten Sie einen festen Arbeitsplatz ein — idealerweise auf einer stabilen Werkbank oder einem alten Tisch.
+ Leinoel und Acrylfarben sind fuer Kinder unbedenklich. Vermeiden Sie Lacke mit Loesungsmitteln.
+
+
+
+ )
+}
diff --git a/levis-holzbau/app/ueber/page.tsx b/levis-holzbau/app/ueber/page.tsx
new file mode 100644
index 0000000..1625d1d
--- /dev/null
+++ b/levis-holzbau/app/ueber/page.tsx
@@ -0,0 +1,83 @@
+'use client'
+
+import { motion } from 'framer-motion'
+import { TreePine, Heart, Sparkles, Users } from 'lucide-react'
+import Link from 'next/link'
+
+const reasons = [
+ { icon: Sparkles, title: 'Kreativitaet', text: 'Du kannst dir selbst ausdenken, was du baust — und es dann wirklich machen!' },
+ { icon: Heart, title: 'Stolz', text: 'Wenn du etwas mit deinen eigenen Haenden baust, macht dich das richtig stolz.' },
+ { icon: TreePine, title: 'Natur', text: 'Holz ist ein natuerliches Material. Du lernst die Natur besser kennen.' },
+ { icon: Users, title: 'Zusammen', text: 'Holzarbeiten macht zusammen mit Freunden oder der Familie am meisten Spass!' },
+]
+
+export default function UeberPage() {
+ return (
+
+
+ Ueber LEVIS Holzbau
+
+ Wir zeigen dir, wie du aus einem einfachen Stueck Holz etwas Tolles machen kannst!
+
+
+
+ {/* Story */}
+
+
Was ist LEVIS Holzbau?
+
+
+ LEVIS Holzbau ist deine Online-Holzwerkstatt! Hier findest du Anleitungen fuer tolle Projekte
+ aus Holz — vom einfachen Zauberstab bis zum echten Vogelhaus.
+
+
+ Jedes Projekt erklaert dir Schritt fuer Schritt, was du tun musst. Du siehst welches Werkzeug
+ und Material du brauchst, und wir zeigen dir immer, worauf du bei der Sicherheit achten musst.
+
+
+ Egal ob du 6 oder 12 Jahre alt bist — fuer jedes Alter gibt es passende Projekte.
+ Faengst du gerade erst an? Dann probier den Zauberstab oder die Nagelbilder. Bist du
+ schon ein Profi? Dann trau dich an den Fliegenpilz!
+
+
+
+
+ {/* Why woodworking */}
+
Warum Holzarbeiten Spass macht
+
+ {reasons.map((r, i) => (
+
+
+
+
+
+
+ ))}
+
+
+ {/* CTA */}
+
+
Bereit loszulegen?
+
Schau dir unsere Projekte an und such dir eins aus!
+
+ Zu den Projekten
+
+
+
+ )
+}
diff --git a/levis-holzbau/components/AgeBadge.tsx b/levis-holzbau/components/AgeBadge.tsx
new file mode 100644
index 0000000..b80f0f4
--- /dev/null
+++ b/levis-holzbau/components/AgeBadge.tsx
@@ -0,0 +1,7 @@
+export function AgeBadge({ range }: { range: string }) {
+ return (
+
+ {range} Jahre
+
+ )
+}
diff --git a/levis-holzbau/components/DifficultyBadge.tsx b/levis-holzbau/components/DifficultyBadge.tsx
new file mode 100644
index 0000000..a347b28
--- /dev/null
+++ b/levis-holzbau/components/DifficultyBadge.tsx
@@ -0,0 +1,15 @@
+import { Hammer } from 'lucide-react'
+
+export function DifficultyBadge({ level }: { level: 1 | 2 | 3 }) {
+ const labels = ['Anfaenger', 'Fortgeschritten', 'Profi']
+ return (
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+ )
+}
diff --git a/levis-holzbau/components/Footer.tsx b/levis-holzbau/components/Footer.tsx
new file mode 100644
index 0000000..763d95e
--- /dev/null
+++ b/levis-holzbau/components/Footer.tsx
@@ -0,0 +1,17 @@
+import { Heart } from 'lucide-react'
+import { Logo } from './Logo'
+
+export function Footer() {
+ return (
+
+
+
+
+
+ Gemacht mit fuer junge Holzwerker
+
+
+
+
+ )
+}
diff --git a/levis-holzbau/components/HeroSection.tsx b/levis-holzbau/components/HeroSection.tsx
new file mode 100644
index 0000000..f09b976
--- /dev/null
+++ b/levis-holzbau/components/HeroSection.tsx
@@ -0,0 +1,95 @@
+'use client'
+
+import { motion } from 'framer-motion'
+import Link from 'next/link'
+import { ArrowRight } from 'lucide-react'
+import { Logo } from './Logo'
+
+export function HeroSection() {
+ return (
+
+
+
+
+
+
+
+ Willkommen in der{' '}
+ Holzwerkstatt !
+
+
+ Hier lernst du, wie man aus Holz tolle Sachen baut und schnitzt.
+ Vom Zauberstab bis zum Vogelhaus — fuer jeden ist etwas dabei!
+
+
+ Entdecke Projekte
+
+
+
+
+
+
+
+
+ )
+}
+
+function HeroIllustration() {
+ return (
+
+ {/* Workbench */}
+
+
+
+
+
+
+ {/* Wood pieces on bench */}
+
+
+
+ {/* Small boat */}
+
+
+
+
+ {/* Hammer */}
+
+
+
+ {/* Tree background */}
+
+
+
+
+
+ {/* Tree right */}
+
+
+
+
+ {/* Sun */}
+
+
+
+ {/* Sawdust particles */}
+
+
+
+
+
+ )
+}
diff --git a/levis-holzbau/components/Logo.tsx b/levis-holzbau/components/Logo.tsx
new file mode 100644
index 0000000..8b31db3
--- /dev/null
+++ b/levis-holzbau/components/Logo.tsx
@@ -0,0 +1,35 @@
+'use client'
+
+export function Logo({ size = 40 }: { size?: number }) {
+ return (
+
+
+ {/* Wood log */}
+
+
+
+ {/* Tree rings */}
+
+
+
+ {/* Saw */}
+
+
+ {/* Saw teeth */}
+
+ {/* Leaf */}
+
+
+
+
+
+
+
+
+
+ LEVIS
+ Holzbau
+
+
+ )
+}
diff --git a/levis-holzbau/components/Navbar.tsx b/levis-holzbau/components/Navbar.tsx
new file mode 100644
index 0000000..4075fcd
--- /dev/null
+++ b/levis-holzbau/components/Navbar.tsx
@@ -0,0 +1,44 @@
+'use client'
+
+import Link from 'next/link'
+import { usePathname } from 'next/navigation'
+import { Logo } from './Logo'
+
+const links = [
+ { href: '/', label: 'Start' },
+ { href: '/projekte', label: 'Projekte' },
+ { href: '/sicherheit', label: 'Sicherheit' },
+ { href: '/ueber', label: 'Ueber LEVIS' },
+]
+
+export function Navbar() {
+ const pathname = usePathname()
+
+ return (
+
+
+
+
+
+
+ {links.map(({ href, label }) => {
+ const isActive = href === '/' ? pathname === '/' : pathname.startsWith(href)
+ return (
+
+ {label}
+
+ )
+ })}
+
+
+
+ )
+}
diff --git a/levis-holzbau/components/ProjectCard.tsx b/levis-holzbau/components/ProjectCard.tsx
new file mode 100644
index 0000000..f37e1d9
--- /dev/null
+++ b/levis-holzbau/components/ProjectCard.tsx
@@ -0,0 +1,42 @@
+'use client'
+
+import Link from 'next/link'
+import { motion } from 'framer-motion'
+import { Clock } from 'lucide-react'
+import { Project } from '@/lib/types'
+import { DifficultyBadge } from './DifficultyBadge'
+import { AgeBadge } from './AgeBadge'
+import { ProjectIllustration } from './ProjectIllustration'
+
+export function ProjectCard({ project }: { project: Project }) {
+ return (
+
+
+
+
+
+
{project.name}
+
{project.description}
+
+
+
+
+ {project.duration}
+
+
+
+
+
+
+ )
+}
diff --git a/levis-holzbau/components/ProjectIllustration.tsx b/levis-holzbau/components/ProjectIllustration.tsx
new file mode 100644
index 0000000..6fbc236
--- /dev/null
+++ b/levis-holzbau/components/ProjectIllustration.tsx
@@ -0,0 +1,132 @@
+'use client'
+
+export function ProjectIllustration({ slug, size = 100 }: { slug: string; size?: number }) {
+ const illustrations: Record = {
+ zauberstab: (
+
+
+
+
+
+
+
+
+ ),
+ untersetzer: (
+
+
+
+
+
+
+
+
+
+ ),
+ nagelbilder: (
+
+
+ {/* Nails forming a star */}
+
+
+
+
+
+ {/* String */}
+
+
+
+ ),
+ bleistiftbox: (
+
+
+
+
+ {/* Pencils */}
+
+
+
+
+
+
+
+ ),
+ segelboot: (
+
+
+
+
+
+ {/* Water */}
+
+
+
+ ),
+ vogelhaus: (
+
+ {/* Roof */}
+
+ {/* Body */}
+
+ {/* Entrance hole */}
+
+ {/* Perch */}
+
+
+ {/* Post */}
+
+ {/* Bird */}
+
+
+
+
+ ),
+ 'holztier-igel': (
+
+ {/* Body */}
+
+ {/* Head */}
+
+ {/* Nose */}
+
+ {/* Eye */}
+
+
+ {/* Spines */}
+ {[0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150].map((angle, i) => {
+ const rad = (angle - 30) * Math.PI / 180
+ const x1 = 55 + Math.cos(rad) * 20
+ const y1 = 52 + Math.sin(rad) * 14
+ const x2 = 55 + Math.cos(rad) * 30
+ const y2 = 52 + Math.sin(rad) * 22
+ return
+ })}
+ {/* Feet */}
+
+
+
+ ),
+ 'schnitzfigur-pilz': (
+
+ {/* Stem */}
+
+
+ {/* Cap */}
+
+
+ {/* White dots */}
+
+
+
+
+
+ {/* Grass */}
+
+
+
+
+ ),
+ }
+
+ return <>{illustrations[slug] || illustrations.zauberstab}>
+}
diff --git a/levis-holzbau/components/SafetyTip.tsx b/levis-holzbau/components/SafetyTip.tsx
new file mode 100644
index 0000000..24ad577
--- /dev/null
+++ b/levis-holzbau/components/SafetyTip.tsx
@@ -0,0 +1,10 @@
+import { AlertTriangle } from 'lucide-react'
+
+export function SafetyTip({ children }: { children: React.ReactNode }) {
+ return (
+
+ )
+}
diff --git a/levis-holzbau/components/StepCard.tsx b/levis-holzbau/components/StepCard.tsx
new file mode 100644
index 0000000..b23cbf3
--- /dev/null
+++ b/levis-holzbau/components/StepCard.tsx
@@ -0,0 +1,15 @@
+import { Step } from '@/lib/types'
+
+export function StepCard({ step, index }: { step: Step; index: number }) {
+ return (
+
+
+ {index + 1}
+
+
+
{step.title}
+
{step.description}
+
+
+ )
+}
diff --git a/levis-holzbau/components/ToolIcon.tsx b/levis-holzbau/components/ToolIcon.tsx
new file mode 100644
index 0000000..6529857
--- /dev/null
+++ b/levis-holzbau/components/ToolIcon.tsx
@@ -0,0 +1,14 @@
+import { Hammer, Scissors, Ruler, Paintbrush, Wrench } from 'lucide-react'
+
+const iconMap: Record = {
+ hammer: Hammer,
+ schnitzmesser: Scissors,
+ lineal: Ruler,
+ pinsel: Paintbrush,
+}
+
+export function ToolIcon({ name }: { name: string }) {
+ const key = name.toLowerCase()
+ const Icon = Object.entries(iconMap).find(([k]) => key.includes(k))?.[1] || Wrench
+ return
+}
diff --git a/levis-holzbau/lib/animations.ts b/levis-holzbau/lib/animations.ts
new file mode 100644
index 0000000..56f5bac
--- /dev/null
+++ b/levis-holzbau/lib/animations.ts
@@ -0,0 +1,15 @@
+export const fadeInUp = {
+ initial: { opacity: 0, y: 20 },
+ animate: { opacity: 1, y: 0 },
+ transition: { duration: 0.5 },
+}
+
+export const staggerContainer = {
+ animate: { transition: { staggerChildren: 0.1 } },
+}
+
+export const scaleIn = {
+ initial: { opacity: 0, scale: 0.9 },
+ animate: { opacity: 1, scale: 1 },
+ transition: { duration: 0.4 },
+}
diff --git a/levis-holzbau/lib/projects.ts b/levis-holzbau/lib/projects.ts
new file mode 100644
index 0000000..2bef4bf
--- /dev/null
+++ b/levis-holzbau/lib/projects.ts
@@ -0,0 +1,214 @@
+import { Project } from './types'
+
+export const projects: Project[] = [
+ {
+ slug: 'zauberstab',
+ name: 'Zauberstab',
+ description: 'Schnitze deinen eigenen magischen Zauberstab aus einem Ast! Mit Schleifpapier und etwas Farbe wird daraus ein echtes Zauberwerkzeug.',
+ ageRange: '6-8',
+ difficulty: 1,
+ duration: '45 Minuten',
+ tools: ['Schnitzmesser (kindersicher)', 'Schleifpapier (fein)', 'Pinsel'],
+ materials: ['1 gerader Ast (ca. 30cm, daumendicke)', 'Acrylfarben', 'Klarlack'],
+ steps: [
+ { title: 'Ast aussuchen', description: 'Such dir einen geraden, trockenen Ast. Er sollte ungefaehr so lang sein wie dein Unterarm und gut in deiner Hand liegen.' },
+ { title: 'Rinde entfernen', description: 'Zieh vorsichtig die Rinde ab. Wenn sie nicht leicht abgeht, hilft ein Erwachsener mit dem Schnitzmesser.' },
+ { title: 'Schleifen', description: 'Schleife den Ast mit dem Schleifpapier glatt. Immer in eine Richtung schleifen — wie beim Streicheln einer Katze!' },
+ { title: 'Spitze formen', description: 'Ein Ende kannst du mit dem Schleifpapier etwas spitzer machen. Nicht zu spitz — es soll ein Zauberstab sein, kein Speer!' },
+ { title: 'Bemalen', description: 'Jetzt wird es bunt! Male Spiralen, Sterne oder Streifen auf deinen Stab. Lass jede Farbe trocknen bevor du die naechste nimmst.' },
+ { title: 'Trocknen lassen', description: 'Stell den Stab zum Trocknen aufrecht in ein Glas. Wenn die Farbe trocken ist, kann ein Erwachsener Klarlack auftragen.' },
+ ],
+ safetyTips: [
+ 'Ein Erwachsener sollte beim Schnitzen immer dabei sein.',
+ 'Immer vom Koerper weg schnitzen!',
+ 'Frische Aeste sind weicher — trockene Aeste koennen splittern.',
+ ],
+ skills: ['Feinmotorik', 'Schleifen', 'Kreatives Gestalten'],
+ },
+ {
+ slug: 'untersetzer',
+ name: 'Holz-Untersetzer',
+ description: 'Bastle praktische Untersetzer aus Holzscheiben! Eine tolle Geschenkidee fuer die ganze Familie.',
+ ageRange: '6+',
+ difficulty: 1,
+ duration: '30 Minuten',
+ tools: ['Schleifpapier (mittel + fein)', 'Pinsel'],
+ materials: ['Holzscheiben (ca. 10cm Durchmesser)', 'Acrylfarben', 'Klarlack', 'Filzgleiter'],
+ steps: [
+ { title: 'Holzscheiben vorbereiten', description: 'Nimm eine Holzscheibe und pruefe ob sie flach auf dem Tisch liegt. Wackelt sie? Dann such dir eine andere aus.' },
+ { title: 'Oberflaeche schleifen', description: 'Schleife beide Seiten der Holzscheibe glatt. Erst mit dem groben, dann mit dem feinen Schleifpapier.' },
+ { title: 'Staub abwischen', description: 'Wisch den Schleifstaub mit einem feuchten Tuch ab. Die Scheibe muss sauber sein damit die Farbe haelt.' },
+ { title: 'Muster malen', description: 'Bemale die Oberseite mit einem schoenen Muster: Blumen, Tiere, Punkte oder Streifen — alles ist erlaubt!' },
+ { title: 'Versiegeln', description: 'Wenn die Farbe trocken ist, traegt ein Erwachsener Klarlack auf. So wird der Untersetzer wasserfest.' },
+ { title: 'Filzgleiter aufkleben', description: 'Klebe 3-4 kleine Filzgleiter auf die Unterseite. So rutscht der Untersetzer nicht und zerkratzt den Tisch nicht.' },
+ ],
+ safetyTips: [
+ 'Beim Schleifen Staub nicht einatmen — am besten draussen arbeiten.',
+ 'Klarlack nur von Erwachsenen auftragen lassen (gut lueften!).',
+ ],
+ skills: ['Schleifen', 'Malen', 'Sorgfaeltiges Arbeiten'],
+ },
+ {
+ slug: 'nagelbilder',
+ name: 'Nagelbilder',
+ description: 'Schlage Naegel in ein Brett und spanne bunte Faeden dazwischen — so entstehen tolle Kunstwerke!',
+ ageRange: '5-7',
+ difficulty: 1,
+ duration: '40 Minuten',
+ tools: ['Hammer (leicht, kindgerecht)', 'Bleistift'],
+ materials: ['Holzbrett (ca. 20x20cm)', 'Kleine Naegel (ca. 20 Stueck)', 'Bunte Wollfaeden', 'Vorlage auf Papier'],
+ steps: [
+ { title: 'Vorlage waehlen', description: 'Such dir eine einfache Form aus: ein Herz, einen Stern oder ein Haus. Zeichne die Form auf Papier und lege es auf das Brett.' },
+ { title: 'Punkte markieren', description: 'Druecke mit dem Bleistift entlang der Form Punkte ins Holz. Alle 2cm ein Punkt reicht aus.' },
+ { title: 'Papier entfernen', description: 'Nimm das Papier vorsichtig ab. Du siehst jetzt die Bleistiftpunkte auf dem Holz.' },
+ { title: 'Naegel einschlagen', description: 'Schlage an jedem Punkt einen Nagel ein. Der Nagel sollte ungefaehr 1cm aus dem Holz schauen. Halt den Nagel mit einer Zange, nicht mit den Fingern!' },
+ { title: 'Faeden spannen', description: 'Knote einen Faden an einen Nagel und spanne ihn kreuz und quer zu den anderen Naegeln. Experimentiere mit verschiedenen Farben!' },
+ { title: 'Aufhaengen', description: 'Schraube eine kleine Oese auf die Rueckseite — fertig ist dein Kunstwerk zum Aufhaengen!' },
+ ],
+ safetyTips: [
+ 'Naegel immer mit einer Zange festhalten, niemals mit den Fingern!',
+ 'Einen leichten Kinderhammer verwenden.',
+ 'Auf eine stabile Unterlage achten beim Haemmern.',
+ ],
+ skills: ['Haemmern', 'Feinmotorik', 'Kreativitaet'],
+ },
+ {
+ slug: 'bleistiftbox',
+ name: 'Bleistiftbox',
+ description: 'Baue eine praktische Box fuer deine Stifte und Pinsel! Aus duennen Holzbrettchen entsteht ein nuetzlicher Schreibtischhelfer.',
+ ageRange: '7-9',
+ difficulty: 2,
+ duration: '1 Stunde',
+ tools: ['Handsaege (kindersicher)', 'Schleifpapier', 'Holzleim', 'Schraubzwinge', 'Lineal', 'Bleistift'],
+ materials: ['Duennes Sperrholz (4mm)', 'Holzleim', 'Acrylfarbe', 'Klarlack'],
+ steps: [
+ { title: 'Teile anzeichnen', description: 'Zeichne die 5 Teile auf das Sperrholz: 1 Boden (8x8cm), 4 Seitenwaende (8x10cm). Miss genau mit dem Lineal!' },
+ { title: 'Aussaegen', description: 'Saege die Teile vorsichtig aus. Ein Erwachsener hilft beim Festhalten. Immer langsam und gleichmaessig saegen.' },
+ { title: 'Kanten schleifen', description: 'Schleife alle Kanten glatt. Besonders die Saegekanten muessen schoen eben werden.' },
+ { title: 'Zusammenleimen', description: 'Trage Holzleim auf die Kanten auf und druecke die Teile zusammen. Erst zwei Seiten an den Boden, dann die anderen zwei.' },
+ { title: 'Trocknen lassen', description: 'Fixiere alles mit Schraubzwingen oder Klebeband. Der Leim braucht mindestens 1 Stunde zum Trocknen.' },
+ { title: 'Dekorieren', description: 'Bemale deine Box mit Acrylfarben. Du kannst deinen Namen draufschreiben oder Muster malen.' },
+ { title: 'Versiegeln', description: 'Nach dem Trocknen der Farbe traegt ein Erwachsener Klarlack auf. Fertig ist deine Bleistiftbox!' },
+ ],
+ safetyTips: [
+ 'Beim Saegen immer das Holz fest einspannen!',
+ 'Die Saege vom Koerper weg fuehren.',
+ 'Holzleim ist nicht giftig, aber trotzdem nicht in den Mund nehmen.',
+ ],
+ skills: ['Messen und Anzeichnen', 'Saegen', 'Leimen', 'Geduld'],
+ },
+ {
+ slug: 'segelboot',
+ name: 'Segelboot',
+ description: 'Baue ein kleines Segelboot das wirklich schwimmt! Perfekt fuer die Badewanne oder den Bach im Park.',
+ ageRange: '8-10',
+ difficulty: 2,
+ duration: '1.5 Stunden',
+ tools: ['Handsaege', 'Schleifpapier', 'Bohrer (Handbohrer)', 'Schnitzmesser'],
+ materials: ['Holzklotz (ca. 20x8x4cm)', 'Rundstab (ca. 20cm)', 'Stoffrest fuer Segel', 'Holzleim', 'Wasserfarbe + Klarlack'],
+ steps: [
+ { title: 'Rumpf anzeichnen', description: 'Zeichne die Bootsform von oben auf den Holzklotz: Vorne spitz, hinten breit. Die typische Bootsform kennst du bestimmt!' },
+ { title: 'Rumpf aussaegen', description: 'Saege die Bootsform aus. Ein Erwachsener hilft beim Festhalten. Die Kurven langsam und vorsichtig saegen.' },
+ { title: 'Rumpf schleifen', description: 'Schleife den Rumpf schoen rund. Die Unterseite sollte leicht gewoelbt sein wie bei einem echten Boot.' },
+ { title: 'Mastloch bohren', description: 'Ein Erwachsener bohrt in der Mitte ein Loch fuer den Mast. Es muss so gross sein, dass der Rundstab genau reinpasst.' },
+ { title: 'Segel basteln', description: 'Schneide aus dem Stoff ein Dreieck aus (ca. 15cm hoch). Klebe oder naehe es am Rundstab fest.' },
+ { title: 'Zusammenbauen', description: 'Stecke den Mast mit etwas Holzleim ins Loch. Lass alles gut trocknen.' },
+ { title: 'Wasserfest machen', description: 'Bemale dein Boot und lass es trocknen. Dann traegt ein Erwachsener mehrere Schichten Klarlack auf — so bleibt dein Boot wasserdicht!' },
+ ],
+ safetyTips: [
+ 'Bohren ist Erwachsenensache — hilf beim Festhalten!',
+ 'Beim Schnitzen immer vom Koerper weg arbeiten.',
+ 'Boot nur unter Aufsicht im Wasser testen.',
+ ],
+ skills: ['Saegen', 'Formen', 'Zusammenbauen', 'Wasserdicht machen'],
+ },
+ {
+ slug: 'vogelhaus',
+ name: 'Vogelhaus',
+ description: 'Baue ein kuscheliges Vogelhaus fuer die Voegel in deinem Garten! Im Winter freuen sie sich besonders ueber ein Futterhaus.',
+ ageRange: '8-10',
+ difficulty: 2,
+ duration: '2 Stunden',
+ tools: ['Handsaege', 'Hammer', 'Schleifpapier', 'Bohrer', 'Lineal', 'Bleistift'],
+ materials: ['Holzbretter (1cm dick)', 'Kleine Naegel oder Schrauben', 'Holzleim', 'Dachpappe oder Rinde', 'Leinoel (ungiftig)'],
+ steps: [
+ { title: 'Teile anzeichnen', description: 'Zeichne alle Teile auf: Boden (18x18cm), 2 Seitenwaende, 2 Giebel (mit Spitze fuer das Dach), 2 Dachhaelften. Ein Erwachsener hilft beim Ausmessen.' },
+ { title: 'Aussaegen', description: 'Saege alle Teile vorsichtig aus. Bei den Giebeln mit der Spitze besonders aufpassen. Immer mit Hilfe eines Erwachsenen!' },
+ { title: 'Einflugsloch', description: 'Ein Erwachsener bohrt in eine Giebelseite ein rundes Loch (ca. 3cm). Das ist die Tuer fuer die Voegel!' },
+ { title: 'Schleifen', description: 'Schleife alle Teile glatt, besonders die Kanten. Voegel sollen sich nicht verletzen.' },
+ { title: 'Zusammenbauen', description: 'Leime und nagle die Teile zusammen: Erst die Seitenwaende am Boden, dann die Giebel, zum Schluss das Dach.' },
+ { title: 'Dach schuetzen', description: 'Klebe Dachpappe oder Rindenstuecke auf das Dach. So bleibt das Innere trocken bei Regen.' },
+ { title: 'Behandeln', description: 'Reibe das Haeuschen von aussen mit Leinoel ein. KEINE Farbe verwenden — die Chemikalien koennten den Voegeln schaden!' },
+ ],
+ safetyTips: [
+ 'Naegel mit der Zange halten beim Einschlagen.',
+ 'Saegen und Bohren nur mit Erwachsenen zusammen.',
+ 'Kein giftiges Holzschutzmittel verwenden — nur Leinoel!',
+ ],
+ skills: ['Messen', 'Saegen', 'Naegeln', 'Zusammenbauen', 'Tierschutz'],
+ },
+ {
+ slug: 'holztier-igel',
+ name: 'Holztier — Igel',
+ description: 'Schnitze einen niedlichen Igel aus Holz! Die Stacheln werden aus kurzen Naegeln oder Zahnstochern gemacht.',
+ ageRange: '8-10',
+ difficulty: 2,
+ duration: '1 Stunde',
+ tools: ['Schnitzmesser (kindersicher)', 'Schleifpapier', 'Bohrer (duenn)', 'Hammer (leicht)'],
+ materials: ['Holzklotz (ca. 10x6x5cm, weiches Holz)', 'Zahnstocher oder kurze Naegel', 'Schwarzer Filzstift', 'Holzleim'],
+ steps: [
+ { title: 'Form anzeichnen', description: 'Zeichne die Igelform von der Seite auf den Holzklotz: Vorne eine kleine Spitznase, hinten rund. Von oben tropfenfoermig.' },
+ { title: 'Grob schnitzen', description: 'Schnitze mit dem Schnitzmesser die grobe Form. Ein Erwachsener hilft bei harten Stellen. Immer vom Koerper weg schnitzen!' },
+ { title: 'Form verfeinern', description: 'Schnitze die Nase spitzer und den Koerper runder. Der Igel soll von hinten huebsch rund aussehen.' },
+ { title: 'Schleifen', description: 'Schleife den ganzen Igel glatt. Besonders das Gesicht soll weich und glatt sein.' },
+ { title: 'Stacheln vorbereiten', description: 'Ein Erwachsener bohrt viele kleine Loecher in den Ruecken (nicht zu tief!). Die Loecher sollten leicht schraeg nach hinten zeigen.' },
+ { title: 'Stacheln einsetzen', description: 'Stecke Zahnstocher in die Loecher und kuerze sie auf 1-2cm. Ein Tropfen Holzleim in jedes Loch haelt die Stacheln fest.' },
+ { title: 'Gesicht malen', description: 'Male mit dem schwarzen Filzstift zwei Augen und eine kleine Nase. Fertig ist dein Igel!' },
+ ],
+ safetyTips: [
+ 'Schnitzmesser immer geschlossen ablegen.',
+ 'Vom Koerper weg schnitzen — das ist die wichtigste Regel!',
+ 'Weiches Holz wie Linde oder Pappel verwenden.',
+ ],
+ skills: ['Schnitzen', 'Feinarbeit', 'Raeumliches Denken'],
+ },
+ {
+ slug: 'schnitzfigur-pilz',
+ name: 'Schnitzfigur — Pilz',
+ description: 'Schnitze einen huebschen Fliegenpilz aus Holz! Ein anspruchsvolles Projekt fuer erfahrene junge Holzwerker.',
+ ageRange: '10-12',
+ difficulty: 3,
+ duration: '2 Stunden',
+ tools: ['Schnitzmesser-Set (3 Messer)', 'Schleifpapier (fein + sehr fein)', 'Schraubstock'],
+ materials: ['Holzklotz (ca. 12x8x8cm, Linde)', 'Acrylfarben (rot, weiss, braun)', 'Klarlack', 'Pinsel (duenn + mittel)'],
+ steps: [
+ { title: 'Entwurf zeichnen', description: 'Zeichne deinen Pilz von vorne und von der Seite auf Papier. Uebertrage die Form mit Bleistift auf den Holzklotz.' },
+ { title: 'Grobe Form', description: 'Spanne den Klotz im Schraubstock ein. Schnitze mit dem groessten Messer die Grundform: oben die runde Kappe, unten den Stiel.' },
+ { title: 'Kappe formen', description: 'Schnitze die Pilzkappe rund und leicht gewoelbt. Die Unterseite der Kappe ist leicht nach innen gewoelbt (hohl).' },
+ { title: 'Stiel formen', description: 'Der Stiel wird nach unten etwas breiter. Schnitze ihn schoen rund und gleichmaessig.' },
+ { title: 'Details schnitzen', description: 'Schnitze mit dem kleinsten Messer feine Details: Die Lamellen unter der Kappe (feine Rillen) und einen kleinen Ring am Stiel.' },
+ { title: 'Feinschliff', description: 'Schleife den ganzen Pilz erst mit feinem, dann mit sehr feinem Schleifpapier. Je glatter, desto schoener die Bemalung!' },
+ { title: 'Bemalen', description: 'Male die Kappe rot mit weissen Punkten (Fliegenpilz!). Der Stiel wird weiss oder hellbraun. Lass jede Schicht gut trocknen.' },
+ ],
+ safetyTips: [
+ 'Dieses Projekt nur mit Schnitz-Erfahrung beginnen!',
+ 'Schraubstock verwenden — niemals das Holz in der Hand halten beim Schnitzen!',
+ 'Scharfe Messer sind sicherer als stumpfe — ein Erwachsener schaerft die Messer.',
+ 'Immer konzentriert arbeiten, nicht ablenken lassen.',
+ ],
+ skills: ['Fortgeschrittenes Schnitzen', 'Detailarbeit', 'Geduld', 'Dreidimensionales Denken'],
+ },
+]
+
+export function getProject(slug: string): Project | undefined {
+ return projects.find((p) => p.slug === slug)
+}
+
+export function getRelatedProjects(slug: string, count = 3): Project[] {
+ const current = getProject(slug)
+ if (!current) return projects.slice(0, count)
+ return projects
+ .filter((p) => p.slug !== slug)
+ .sort((a, b) => Math.abs(a.difficulty - current.difficulty) - Math.abs(b.difficulty - current.difficulty))
+ .slice(0, count)
+}
diff --git a/levis-holzbau/lib/types.ts b/levis-holzbau/lib/types.ts
new file mode 100644
index 0000000..99b4ba9
--- /dev/null
+++ b/levis-holzbau/lib/types.ts
@@ -0,0 +1,18 @@
+export interface Project {
+ slug: string
+ name: string
+ description: string
+ ageRange: string
+ difficulty: 1 | 2 | 3
+ duration: string
+ tools: string[]
+ materials: string[]
+ steps: Step[]
+ safetyTips: string[]
+ skills: string[]
+}
+
+export interface Step {
+ title: string
+ description: string
+}
diff --git a/levis-holzbau/next-env.d.ts b/levis-holzbau/next-env.d.ts
new file mode 100644
index 0000000..830fb59
--- /dev/null
+++ b/levis-holzbau/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/levis-holzbau/next.config.js b/levis-holzbau/next.config.js
new file mode 100644
index 0000000..5cd8cc3
--- /dev/null
+++ b/levis-holzbau/next.config.js
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ output: 'standalone',
+}
+
+module.exports = nextConfig
diff --git a/levis-holzbau/package-lock.json b/levis-holzbau/package-lock.json
new file mode 100644
index 0000000..8da2332
--- /dev/null
+++ b/levis-holzbau/package-lock.json
@@ -0,0 +1,2017 @@
+{
+ "name": "levis-holzbau",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "levis-holzbau",
+ "version": "1.0.0",
+ "dependencies": {
+ "framer-motion": "^11.15.0",
+ "lucide-react": "^0.468.0",
+ "next": "^15.1.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.2",
+ "@types/react": "^18.3.14",
+ "@types/react-dom": "^18.3.5",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.16",
+ "typescript": "^5.7.2"
+ }
+ },
+ "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.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "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.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "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.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "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.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "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.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "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.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "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.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "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.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "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.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "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-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "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-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "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.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "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-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "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.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "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/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "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/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz",
+ "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==",
+ "license": "MIT"
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz",
+ "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz",
+ "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz",
+ "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz",
+ "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz",
+ "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz",
+ "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz",
+ "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz",
+ "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==",
+ "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/@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/node": {
+ "version": "22.19.15",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
+ "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "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/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/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/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.30001777",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+ "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
+ "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/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/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/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.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "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/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/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/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "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/framer-motion": {
+ "version": "11.18.2",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
+ "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^11.18.1",
+ "motion-utils": "^11.18.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "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/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/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/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-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-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-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-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/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==",
+ "license": "MIT"
+ },
+ "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/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==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.468.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
+ "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "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/motion-dom": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
+ "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^11.18.1"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz",
+ "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
+ "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.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "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/next": {
+ "version": "15.5.12",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz",
+ "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "15.5.12",
+ "@swc/helpers": "0.5.15",
+ "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.5.12",
+ "@next/swc-darwin-x64": "15.5.12",
+ "@next/swc-linux-arm64-gnu": "15.5.12",
+ "@next/swc-linux-arm64-musl": "15.5.12",
+ "@next/swc-linux-x64-gnu": "15.5.12",
+ "@next/swc-linux-x64-musl": "15.5.12",
+ "@next/swc-win32-arm64-msvc": "15.5.12",
+ "@next/swc-win32-x64-msvc": "15.5.12",
+ "sharp": "^0.34.3"
+ },
+ "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_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/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/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.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "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.11",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "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
+ }
+ }
+ },
+ "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/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": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "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/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "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/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "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/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "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"
+ }
+ },
+ "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/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.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "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.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "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.7",
+ "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 || ^5.0 || ^6.0",
+ "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/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/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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-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/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/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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"
+ }
+ }
+}
diff --git a/levis-holzbau/package.json b/levis-holzbau/package.json
new file mode 100644
index 0000000..134067c
--- /dev/null
+++ b/levis-holzbau/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "levis-holzbau",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev -p 3013",
+ "build": "next build",
+ "start": "next start -p 3013"
+ },
+ "dependencies": {
+ "framer-motion": "^11.15.0",
+ "lucide-react": "^0.468.0",
+ "next": "^15.1.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.2",
+ "@types/react": "^18.3.14",
+ "@types/react-dom": "^18.3.5",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.16",
+ "typescript": "^5.7.2"
+ }
+}
diff --git a/levis-holzbau/postcss.config.mjs b/levis-holzbau/postcss.config.mjs
new file mode 100644
index 0000000..0dc456a
--- /dev/null
+++ b/levis-holzbau/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ },
+}
+
+export default config
diff --git a/levis-holzbau/tailwind.config.ts b/levis-holzbau/tailwind.config.ts
new file mode 100644
index 0000000..738a127
--- /dev/null
+++ b/levis-holzbau/tailwind.config.ts
@@ -0,0 +1,26 @@
+import type { Config } from 'tailwindcss'
+
+const config: Config = {
+ content: [
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ theme: {
+ extend: {
+ colors: {
+ primary: '#F5A623',
+ secondary: '#4CAF50',
+ accent: '#2196F3',
+ warning: '#FFC107',
+ cream: '#FDF8F0',
+ dark: '#2C2C2C',
+ },
+ fontFamily: {
+ heading: ['Quicksand', 'sans-serif'],
+ body: ['Nunito', 'sans-serif'],
+ },
+ },
+ },
+ plugins: [],
+}
+export default config
diff --git a/levis-holzbau/tsconfig.json b/levis-holzbau/tsconfig.json
new file mode 100644
index 0000000..d81d4ee
--- /dev/null
+++ b/levis-holzbau/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ },
+ "target": "ES2017"
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/rag-service/qdrant_client_wrapper.py b/rag-service/qdrant_client_wrapper.py
index 565402a..daeafda 100644
--- a/rag-service/qdrant_client_wrapper.py
+++ b/rag-service/qdrant_client_wrapper.py
@@ -27,6 +27,8 @@ _COMPLIANCE_COLLECTIONS = {
"bp_compliance_gdpr": 1024,
"bp_compliance_schulrecht": 1024,
"bp_compliance_datenschutz": 1024,
+ "bp_compliance_gesetze": 1024,
+ "bp_compliance_ce": 1024,
"bp_dsfa_templates": 1024,
"bp_dsfa_risks": 1024,
}
@@ -107,6 +109,13 @@ class QdrantClientWrapper:
# Indexing
# ------------------------------------------------------------------
+ async def ensure_collection(self, name: str, vector_size: int = 1024) -> None:
+ """Create collection if it doesn't exist."""
+ try:
+ self.client.get_collection(name)
+ except Exception:
+ await self.create_collection(name, vector_size)
+
async def index_documents(
self,
collection: str,
@@ -120,6 +129,10 @@ class QdrantClientWrapper:
f"vectors ({len(vectors)}) and payloads ({len(payloads)}) must have equal length"
)
+ # Auto-create collection if missing
+ vector_size = len(vectors[0]) if vectors else 1024
+ await self.ensure_collection(collection, vector_size)
+
if ids is None:
ids = [str(uuid.uuid4()) for _ in vectors]