diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d70bb9c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+.DS_Store
+*.local
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..98360bf
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,23 @@
+# Build stage
+FROM node:22-alpine AS builder
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . ./
+RUN npm run build
+
+# Production stage
+FROM nginx:alpine
+
+COPY --from=builder /app/dist /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 80
+
+HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
+ CMD wget --quiet --tries=1 --spider http://localhost:80/ || exit 1
+
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..db58498
--- /dev/null
+++ b/README.md
@@ -0,0 +1,49 @@
+# Glimpse
+
+> **Glimpse** — A procedural, skeuomorphic paper desk, sticky notes, photo, and visual moodboard workspace.
+
+Inspired by creative coding desktop experiments, Glimpse renders tactile sheets of paper, Polaroid prints, luggage tags, crumpled receipts, and stationery on realistic desk surfaces with procedural paper grain shaders, dynamic 360° directional lighting, and realistic momentum drag-and-throw physics.
+
+---
+
+## Features
+
+- **Procedural Paper Texture & Grain**: Dynamic SVG fractal noise filters simulating authentic physical paper fibers.
+- **Dynamic 360° Directional Lighting**: Interactive `Light` angle control casting synchronized vector shadows across all sheets and props.
+- **Tactile Sound Synthesis**: Built-in Web Audio API synthesizer for paper rustle, soft drop thuds, throw whooshes, tape peel, and pin taps without external audio assets.
+- **Physics Engine ("Flick to Throw")**: Momentum drag-and-throw mechanics with velocity tracking, rotational inertia, air damping, and boundary collisions.
+- **Layout Toggling**: Instant transition between organic desktop disarray (`Scatter`) and clean moodboards (`Grid`).
+- **Freehand Pen Doodling**: Draw, underline, cross out tasks, or sketch arrows directly onto cards or the desk with smooth ink strokes.
+- **Multiple Desk Textures**: Oak wood, walnut, self-healing cutting mat, corkboard, blueprint drafting, dark slate, and Italian Carrara marble.
+- **Card Variety**:
+ - Sticky notes with interactive to-do checkboxes
+ - Polaroid instant photos with mounting corners
+ - Kraft luggage tags with brass eyelets & twine
+ - Ripped memo sheets with typewriter lettering
+ - Minimalist business cards
+ - Itemized store receipts with barcodes
+ - Hand-drawn SVG graph and flowchart sketches
+ - Washi tape strips, paperclips, and pushpins
+
+---
+
+## Keyboard Shortcuts
+
+- `S` or `Space` — **Shuffle** cards across the desk
+- `G` — Toggle **Grid** vs. **Scatter** layout
+- `P` — Toggle freehand **Pen** mode
+- `N` — **Add New** custom card
+- `M` — **Replay** entry motion
+- `Double-click` — **Edit** or delete card
+
+---
+
+## Self-Hosting with Docker & Traefik
+
+Glimpse runs in a lightweight, multi-stage Alpine Nginx container:
+
+```bash
+docker compose up -d --build
+```
+
+Configured for Traefik reverse proxy via dynamic rules on `t3_proxy` network at `https://glimpse.vloser.us`.
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..84368f1
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,21 @@
+services:
+ glimpse:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: glimpse
+ restart: unless-stopped
+ networks:
+ - t3_proxy
+ ports:
+ - "127.0.0.1:8476:80"
+ healthcheck:
+ test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:80/ || exit 1"]
+ interval: 30s
+ timeout: 5s
+ retries: 3
+ start_period: 10s
+
+networks:
+ t3_proxy:
+ external: true
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..c62949b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ Glimpse — Procedural Paper Desk & Workspace
+
+
+
+
+
+
+
+
+
+
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..9f63fe0
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,23 @@
+server {
+ listen 80;
+ server_name localhost;
+
+ location / {
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ try_files $uri $uri/ /index.html;
+ }
+
+ # Cache static assets
+ location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|woff2?|eot|ttf|otf)$ {
+ root /usr/share/nginx/html;
+ expires 1y;
+ add_header Cache-Control "public, max-age=31536000, immutable";
+ access_log off;
+ }
+
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+}
diff --git a/og_image.jpg b/og_image.jpg
new file mode 100644
index 0000000..5949fa9
Binary files /dev/null and b/og_image.jpg differ
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..1486251
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2856 @@
+{
+ "name": "paper-desk",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "paper-desk",
+ "version": "1.0.0",
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "lucide-react": "^1.16.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwind-merge": "^3.0.2"
+ },
+ "devDependencies": {
+ "@types/node": "^22.13.9",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.5.3",
+ "tailwindcss": "^3.4.17",
+ "typescript": "^5.8.2",
+ "vite": "^6.2.0"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz",
+ "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz",
+ "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.10",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz",
+ "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.9",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@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.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "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/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "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/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.5.tgz",
+ "integrity": "sha512-MrfgRQSpD5hkkRg6XI16B6EDLb+6+xp8xje6bpdSwkYw+qxrBKbUVrRAQu9h82UT+QumSIVQ8HXsXjPd2dlhqw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.5.tgz",
+ "integrity": "sha512-+uu2p/R9Tzn8GpoQyX1pAo5dmKT1DhXevfaCzqzmPFhnLjHLWw7uWkaVGlxmhwYoRiYYhsGfRcD5cTBpL7rgSw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.5.tgz",
+ "integrity": "sha512-17dys+YEmzwzzFVHQAVDONg5FALXY9kd99HFEpfk4gJVHYxoh5bGea3D4t1fXXnwkTcOe25wPyd6nWS0I8IdLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.5.tgz",
+ "integrity": "sha512-wg0zQEKkEvFZf1UaaX1UELOoPs//OSKUG7D+yITuqOsAvza7LvHTv5Wu4nmMfzNG7EYI2jp2PJC7Id6OETmsYQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.5.tgz",
+ "integrity": "sha512-CoJmWcpEMuwin+TZ/ev/H9uDyF/uxUKQSWXdmIPn/UBx/uGV9+4ebMp0FwnlhWR9PE1AdzYsKf3bZevhif38+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.5.tgz",
+ "integrity": "sha512-PxZnLAaTUAL5xG0W2BXLeYPKrK2b3LIASV2/e/Kf7PZvSLI1iAQIm9B9aJD+YyFIT7hc6qaMV3+OOO35R28LfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.5.tgz",
+ "integrity": "sha512-uPo3IvnONjhbFXwWgroBeFwOicuhT7ZwQrnzkzmOtBJzeQbkUJUE1SJDfDI31ijQ5+ZUIA6sPrdUMxTSnxUWAg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.5.tgz",
+ "integrity": "sha512-ywI8Jyuc4LyHYTjC0mw4yA1ulvZBkHusdpWnoiEBsmBuHvSnt90btTBGCZ+s/2y+YNNfVAEKy33nPo5UI9zTmg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.5.tgz",
+ "integrity": "sha512-tmuXJoQ/3ovSr637aykZMdQteU/CB2APAz2CrvsbibuoHZi1BS1Db13cVViBdEARGwXCLn1DSJ8wsjoThLJPGA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.5.tgz",
+ "integrity": "sha512-5FMjSYNcUOHx7oW8Qu9KB6jGPpvF/8sG7+BDHwU/Ihh1/6FMJ9ySmsFG4tJ1qbNRuP8OLzCBDso4sF0mQaLuFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.5.tgz",
+ "integrity": "sha512-K8ZJ+neLDbUkIrL8dqF/5LtuVCrKwo27kREgOlxjBEjlOXwHvDnHLU8Z/UKv/B49qi7e8hsSDOyoJwhwqhvX4w==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.5.tgz",
+ "integrity": "sha512-NlVxsq7PFPv0DBKGuNWRDKixQYQCjM53zl04mxEcKMBmJbCBIUm1JdPfIWDRHyOOFzzYPmLQzfeEwh4716ayQg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.5.tgz",
+ "integrity": "sha512-ARhRHJYs5eCFet5azfrHD97vOkD8OzNW2RSMlvo5CcBqXaTOLfp0OHDtUQh7/K5AL5fUYoFp2fgC9KDg4vVYMQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.5.tgz",
+ "integrity": "sha512-atSgreM5C7jpxd9aEXN+6vJ5wTd05iYw+S3FiQpw7VsoEl8B90llbq35ARON+GOHj5QlVQEq2vOKWCHTob/BSQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.5.tgz",
+ "integrity": "sha512-vmlmwBFGnjVWxSRoVabNkyYuscSmg52Pd0fM8jEbNT3Jmka4A6HcB84x4uAmuumznWfBVaxKOqU6jbEgomWKCA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.5.tgz",
+ "integrity": "sha512-hhJrP6S9WkbsbcHgUkvCBS5Cn8IdsG8XjlLZyxjnv9+NdoYfbbZE4TWJYr8apdaj1M5t5Y1id2StnEslxpNFyA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.5.tgz",
+ "integrity": "sha512-NNVCyN9pGrwFmQa4bf+Cnf5aVH3g6ZpnlOg1Cs/H9swIHT9bRixijfzUqyEIWm1WtiAuR1LJ+rJH6zpBm8kgQA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.5.tgz",
+ "integrity": "sha512-dR9nd6OKnvSS60Ifz/L7Mj5zzYyoqyRn2NwTuaOONKaa3xsVGXyp8YusMbvFyEOR94DxnwgcbgsFqkHd29ZSzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.5.tgz",
+ "integrity": "sha512-1tzsMjaqhsh3bAO6SJiJqh8hIr9sSPRd+i2vPPxJHod8/XYnf9TEnK0z/u3r1z1AkI347X0KbRSg9dWcxeGahw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.5.tgz",
+ "integrity": "sha512-01mWMQblKoXXsmk28KXtpbA96w7g/aOWUoV3AC16fdHdqkykGalqyyM+5u8M7uP5/MEMiiPv69Hv7iv/hEH/dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.5.tgz",
+ "integrity": "sha512-trdnMPb/DNm4V/CgsaoGgr90HfQQ071wfK0KoIs4YpLvjBgVQGXhGabg2Z6nK4EcYNilrb9ZZPcsP6+zpJIdxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.5.tgz",
+ "integrity": "sha512-x2LxZlfdfsw99T6/qb9EqE/Qi84JWzibjyOJyhJsgFJy6CafaTiqIXCCnslCGrV5XWNh7fwrS3FbRENcuKnooA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.5.tgz",
+ "integrity": "sha512-/Qd/9aDQIo0+jmItgBmPIs8195YVbDxRRApn47GyufgbutCDOh7eP5vSKKlI4QNFeaI+wKQAkSMsPnF3EuMpaA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.5.tgz",
+ "integrity": "sha512-ngUJ/XvOWrqbd2IMsfXfh2xXFLTP3sY7sCKvxbGvYdBZ1KmXFd3ewDZ9n3/GNzAUgzRuhLT/UvJRivbNoAQRBw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.5.tgz",
+ "integrity": "sha512-IhV6ew/DuzUWwHaYmlh7MrPCV3lKFUq/amTEwysi4r/mwNPAEfAMh5dYWeTSPTeMy2pFHGMEWYtPkHzfX3tG5A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.20.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
+ "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.3.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.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/autoprefixer": {
+ "version": "10.6.1",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz",
+ "integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.9",
+ "caniuse-lite": "^1.0.30001810",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.28",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz",
+ "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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/browserslist": {
+ "version": "4.29.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
+ "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.26",
+ "caniuse-lite": "^1.0.30001813",
+ "electron-to-chromium": "^1.5.439",
+ "node-releases": "^2.0.57",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "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.30001815",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz",
+ "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==",
+ "dev": true,
+ "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/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "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/electron-to-chromium": {
+ "version": "1.5.452",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.452.tgz",
+ "integrity": "sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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.3",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz",
+ "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==",
+ "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/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "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/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "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.17.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz",
+ "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.4"
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "1.55.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.55.0.tgz",
+ "integrity": "sha512-bSozQHiI3/8q7JvgoqVRGOYUi7fYT3siAZwmZsljjsOH2Husp/2ODdFf/leD+ZC/2yKfn7r8oh6yeoR3ITR5vA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "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/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
+ "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/node-releases": {
+ "version": "2.0.58",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz",
+ "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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.30",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz",
+ "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==",
+ "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.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "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.4",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz",
+ "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==",
+ "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": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
+ "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.3.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz",
+ "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": "^19.3.0"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz",
+ "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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.13",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.13.tgz",
+ "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.17.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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/rollup": {
+ "version": "4.64.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.5.tgz",
+ "integrity": "sha512-EAP5ZHcz2ijbgCWwPkpI7zh6bfsri2Ez5FtCy2dCp1k62duXzfG7DLGGh7RU2QQXWwavT+u4qpvDF/TWOiiGTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.64.5",
+ "@rollup/rollup-android-arm64": "4.64.5",
+ "@rollup/rollup-darwin-arm64": "4.64.5",
+ "@rollup/rollup-darwin-x64": "4.64.5",
+ "@rollup/rollup-freebsd-arm64": "4.64.5",
+ "@rollup/rollup-freebsd-x64": "4.64.5",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.64.5",
+ "@rollup/rollup-linux-arm-musleabihf": "4.64.5",
+ "@rollup/rollup-linux-arm64-gnu": "4.64.5",
+ "@rollup/rollup-linux-arm64-musl": "4.64.5",
+ "@rollup/rollup-linux-loong64-gnu": "4.64.5",
+ "@rollup/rollup-linux-loong64-musl": "4.64.5",
+ "@rollup/rollup-linux-ppc64-gnu": "4.64.5",
+ "@rollup/rollup-linux-ppc64-musl": "4.64.5",
+ "@rollup/rollup-linux-riscv64-gnu": "4.64.5",
+ "@rollup/rollup-linux-riscv64-musl": "4.64.5",
+ "@rollup/rollup-linux-s390x-gnu": "4.64.5",
+ "@rollup/rollup-linux-x64-gnu": "4.64.5",
+ "@rollup/rollup-linux-x64-musl": "4.64.5",
+ "@rollup/rollup-openbsd-x64": "4.64.5",
+ "@rollup/rollup-openharmony-arm64": "4.64.5",
+ "@rollup/rollup-win32-arm64-msvc": "4.64.5",
+ "@rollup/rollup-win32-ia32-msvc": "4.64.5",
+ "@rollup/rollup-win32-x64-gnu": "4.64.5",
+ "@rollup/rollup-win32-x64-msvc": "4.64.5",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "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.28.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
+ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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/tailwind-merge": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz",
+ "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "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.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "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.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "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/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/update-browserslist-db": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
+ "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.4.tgz",
+ "integrity": "sha512-xGjU/SyjeNyb0E4KxXBvYz/1C+3JXuujvf2hXNOtnlcE+58o5ajd9N2/n2VhkiJvpCP2qQasnUpx5PXNeI+p1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/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/vite/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6b9ca88
--- /dev/null
+++ b/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "glimpse",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "lucide-react": "^1.16.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "tailwind-merge": "^3.0.2"
+ },
+ "devDependencies": {
+ "@types/node": "^22.13.9",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.5.3",
+ "tailwindcss": "^3.4.17",
+ "typescript": "^5.8.2",
+ "vite": "^6.2.0"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..56a56e7
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,643 @@
+import React, { useState, useEffect, useRef, useCallback } from 'react';
+import { CardItem, DeskProp, DeskSettings, InkStroke } from './types';
+import { generateProceduralCards, SAMPLE_PRESETS } from './utils/proceduralCards';
+import { soundEngine } from './utils/sound';
+import { TopBar } from './components/TopBar';
+import { BottomBar } from './components/BottomBar';
+import { DeskBackground } from './components/DeskTextures';
+import { PaperCard } from './components/PaperCard';
+import { DeskPropsLayer } from './components/DeskProps';
+import { PenLayer } from './components/PenLayer';
+import { AddCardModal, EditCardModal } from './components/AddCardModal';
+import { PortfolioModal } from './components/PortfolioModal';
+
+const INITIAL_PROPS: DeskProp[] = [
+ { id: 'prop-pencil-1', type: 'pencil', x: 120, y: 160, rotation: 38, zIndex: 12 },
+ { id: 'prop-pen-1', type: 'pen', x: 890, y: 170, rotation: -28, zIndex: 15 },
+ { id: 'prop-pencil-2', type: 'pencil', x: 230, y: 720, rotation: -42, zIndex: 10 },
+ { id: 'prop-pen-2', type: 'pen', x: 440, y: 880, rotation: 92, zIndex: 14 },
+ { id: 'prop-tape-1', type: 'tape-roll', x: 410, y: 120, rotation: 12, zIndex: 9 },
+ { id: 'prop-clip-1', type: 'paperclip', x: 640, y: 760, rotation: 55, zIndex: 8 },
+ { id: 'prop-clip-2', type: 'paperclip', x: 1190, y: 920, rotation: -35, zIndex: 8 },
+ { id: 'prop-pin-1', type: 'pin', x: 1210, y: 130, rotation: 0, zIndex: 16 }
+];
+
+export const App: React.FC = () => {
+ // Desk Settings matching the original Paper Desk UI
+ const [settings, setSettings] = useState({
+ lightAngle: 225, // Exactly 225° from Maksim's post
+ grain: 0.70, // Exactly 0.70 Grain
+ attachChance: 0.55, // Exactly 55% Attach
+ cardCount: 22, // High-density aesthetic desk
+ deskTexture: 'oak', // Oak desk
+ layoutMode: 'scatter',
+ fontMode: 'mixed',
+ showProps: true,
+ enableMotion: true,
+ penMode: false,
+ soundEnabled: false,
+ activeFilter: 'all'
+ });
+
+ const [cards, setCards] = useState([]);
+ const [propsList, setPropsList] = useState(INITIAL_PROPS);
+ const [strokes, setStrokes] = useState([]);
+ const [maxZIndex, setMaxZIndex] = useState(50);
+
+ // Modals state
+ const [isAddOpen, setIsAddOpen] = useState(false);
+ const [editingCard, setEditingCard] = useState(null);
+ const [isPortfolioOpen, setIsPortfolioOpen] = useState(false);
+
+ // Dragging & Physics references
+ const draggingIdRef = useRef(null);
+ const dragTypeRef = useRef<'card' | 'prop'>('card');
+ const dragOffsetRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
+ const pointerHistoryRef = useRef<{ x: number; y: number; time: number }[]>([]);
+ const [draggingCardId, setDraggingCardId] = useState(null);
+
+ // Initialize cards on mount
+ useEffect(() => {
+ const w = typeof window !== 'undefined' ? window.innerWidth : 1400;
+ const h = typeof window !== 'undefined' ? window.innerHeight : 900;
+ const initialCards = generateProceduralCards(
+ settings.cardCount,
+ w,
+ h,
+ settings.attachChance,
+ settings.fontMode
+ );
+ setCards(initialCards);
+ setMaxZIndex(initialCards.length + 20);
+ }, []);
+
+ // Update card count when slider changes
+ const updateSettings = (newSettings: Partial) => {
+ setSettings((prev) => {
+ const updated = { ...prev, ...newSettings };
+
+ if (newSettings.cardCount !== undefined && newSettings.cardCount !== prev.cardCount) {
+ const w = window.innerWidth;
+ const h = window.innerHeight;
+ const diff = newSettings.cardCount - cards.length;
+ if (diff > 0) {
+ const added = generateProceduralCards(diff, w, h, updated.attachChance, updated.fontMode);
+ setCards((c) => [...c, ...added]);
+ } else if (diff < 0) {
+ setCards((c) => c.slice(0, newSettings.cardCount));
+ }
+ }
+
+ if (newSettings.attachChance !== undefined && newSettings.attachChance !== prev.attachChance) {
+ setCards((c) =>
+ c.map((card) => {
+ if (Math.random() < (newSettings.attachChance ?? 0.55)) {
+ const types: CardItem['attachType'][] = ['washi', 'pin', 'paperclip'];
+ return { ...card, attachType: types[Math.floor(Math.random() * types.length)] };
+ }
+ return { ...card, attachType: 'none' };
+ })
+ );
+ }
+
+ return updated;
+ });
+ };
+
+ // Re-shuffle cards
+ const handleShuffle = useCallback(() => {
+ soundEngine.shuffle(settings.soundEnabled);
+ const w = window.innerWidth;
+ const h = window.innerHeight;
+
+ setCards((prev) => {
+ return prev.map((card, i) => {
+ const padding = 100;
+ const targetX = padding + Math.random() * (w - card.width - padding * 2);
+ const targetY = padding + Math.random() * (h - card.height - padding * 2);
+ const randRot = (Math.random() - 0.5) * 24;
+
+ return {
+ ...card,
+ x: targetX,
+ y: targetY,
+ rotation: randRot,
+ vx: (Math.random() - 0.5) * 4,
+ vy: (Math.random() - 0.5) * 4,
+ zIndex: i + 1
+ };
+ });
+ });
+ }, [settings.soundEnabled]);
+
+ // Arrange in Grid mode
+ const arrangeGrid = useCallback(() => {
+ soundEngine.rustle(settings.soundEnabled);
+ const w = window.innerWidth;
+ const h = window.innerHeight;
+ const paddingX = 120;
+ const paddingY = 110;
+ const availableW = w - paddingX * 2;
+ const availableH = h - paddingY * 2;
+
+ const cols = Math.ceil(Math.sqrt(cards.length * 1.4));
+ const rows = Math.ceil(cards.length / cols);
+
+ const cellW = availableW / cols;
+ const cellH = availableH / rows;
+
+ setCards((prev) =>
+ prev.map((card, idx) => {
+ const col = idx % cols;
+ const row = Math.floor(idx / cols);
+
+ return {
+ ...card,
+ x: paddingX + col * cellW + (cellW - card.width) / 2,
+ y: paddingY + row * cellH + (cellH - card.height) / 2,
+ rotation: (Math.random() - 0.5) * 4, // Subtle organic alignment
+ vx: 0,
+ vy: 0
+ };
+ })
+ );
+ }, [cards.length, settings.soundEnabled]);
+
+ // Trigger grid when layoutMode changes
+ useEffect(() => {
+ if (settings.layoutMode === 'grid') {
+ arrangeGrid();
+ } else {
+ handleShuffle();
+ }
+ }, [settings.layoutMode]);
+
+ // Physics animation loop on requestAnimationFrame
+ useEffect(() => {
+ if (!settings.enableMotion) return;
+
+ let animId: number;
+
+ const physicsLoop = () => {
+ setCards((prevCards) => {
+ let hasMotion = false;
+ const updated = prevCards.map((card) => {
+ // If being dragged, physics doesn't apply to it directly
+ if (card.id === draggingIdRef.current) return card;
+
+ const speed = Math.hypot(card.vx, card.vy);
+ if (speed < 0.05) return card;
+
+ hasMotion = true;
+ let newX = card.x + card.vx;
+ let newY = card.y + card.vy;
+ let newVx = card.vx * 0.92; // Friction damping
+ let newVy = card.vy * 0.92;
+
+ // Screen edge bounce
+ const maxX = window.innerWidth - card.width - 20;
+ const maxY = window.innerHeight - card.height - 20;
+
+ if (newX < 20) {
+ newX = 20;
+ newVx *= -0.5;
+ } else if (newX > maxX) {
+ newX = maxX;
+ newVx *= -0.5;
+ }
+
+ if (newY < 60) {
+ newY = 60;
+ newVy *= -0.5;
+ } else if (newY > maxY) {
+ newY = maxY;
+ newVy *= -0.5;
+ }
+
+ return {
+ ...card,
+ x: newX,
+ y: newY,
+ vx: newVx,
+ vy: newVy
+ };
+ });
+
+ return hasMotion ? updated : prevCards;
+ });
+
+ animId = requestAnimationFrame(physicsLoop);
+ };
+
+ animId = requestAnimationFrame(physicsLoop);
+ return () => cancelAnimationFrame(animId);
+ }, [settings.enableMotion]);
+
+ // Pointer Down on a Card
+ const handleCardPointerDown = (e: React.PointerEvent, card: CardItem) => {
+ if (settings.penMode) return;
+ e.stopPropagation();
+
+ const newZ = maxZIndex + 1;
+ setMaxZIndex(newZ);
+
+ draggingIdRef.current = card.id;
+ dragTypeRef.current = 'card';
+ setDraggingCardId(card.id);
+
+ dragOffsetRef.current = {
+ x: e.clientX - card.x,
+ y: e.clientY - card.y
+ };
+
+ pointerHistoryRef.current = [{ x: e.clientX, y: e.clientY, time: Date.now() }];
+
+ soundEngine.rustle(settings.soundEnabled, 1.0);
+
+ setCards((prev) =>
+ prev.map((c) => (c.id === card.id ? { ...c, zIndex: newZ, vx: 0, vy: 0 } : c))
+ );
+ };
+
+ // Pointer Down on a Prop
+ const handlePropPointerDown = (e: React.PointerEvent, prop: DeskProp) => {
+ if (settings.penMode) return;
+ e.stopPropagation();
+
+ const newZ = maxZIndex + 1;
+ setMaxZIndex(newZ);
+
+ draggingIdRef.current = prop.id;
+ dragTypeRef.current = 'prop';
+
+ dragOffsetRef.current = {
+ x: e.clientX - prop.x,
+ y: e.clientY - prop.y
+ };
+
+ pointerHistoryRef.current = [{ x: e.clientX, y: e.clientY, time: Date.now() }];
+
+ soundEngine.rustle(settings.soundEnabled, 0.5);
+
+ setPropsList((prev) =>
+ prev.map((p) => (p.id === prop.id ? { ...p, zIndex: newZ } : p))
+ );
+ };
+
+ // Global Pointer Move
+ const handleGlobalPointerMove = (e: React.PointerEvent) => {
+ if (!draggingIdRef.current) return;
+
+ const currentX = e.clientX;
+ const currentY = e.clientY;
+ const targetX = currentX - dragOffsetRef.current.x;
+ const targetY = currentY - dragOffsetRef.current.y;
+
+ // Track pointer history for momentum / flick
+ pointerHistoryRef.current.push({ x: currentX, y: currentY, time: Date.now() });
+ if (pointerHistoryRef.current.length > 5) {
+ pointerHistoryRef.current.shift();
+ }
+
+ if (dragTypeRef.current === 'card') {
+ setCards((prev) =>
+ prev.map((c) => (c.id === draggingIdRef.current ? { ...c, x: targetX, y: targetY } : c))
+ );
+ } else {
+ setPropsList((prev) =>
+ prev.map((p) => (p.id === draggingIdRef.current ? { ...p, x: targetX, y: targetY } : p))
+ );
+ }
+ };
+
+ // Global Pointer Up (Release with Momentum / Throw)
+ const handleGlobalPointerUp = () => {
+ if (!draggingIdRef.current) return;
+
+ const id = draggingIdRef.current;
+ draggingIdRef.current = null;
+ setDraggingCardId(null);
+
+ // Calculate throw velocity from recent pointer samples
+ const history = pointerHistoryRef.current;
+ let vx = 0;
+ let vy = 0;
+
+ if (history.length >= 2) {
+ const recent = history[history.length - 1];
+ const older = history[0];
+ const dt = (recent.time - older.time) || 16;
+ if (dt < 250) {
+ vx = ((recent.x - older.x) / dt) * 14;
+ vy = ((recent.y - older.y) / dt) * 14;
+ }
+ }
+
+ const speed = Math.hypot(vx, vy);
+
+ if (dragTypeRef.current === 'card') {
+ if (speed > 4) {
+ soundEngine.flick(settings.soundEnabled);
+ } else {
+ soundEngine.drop(settings.soundEnabled);
+ }
+
+ setCards((prev) =>
+ prev.map((c) =>
+ c.id === id
+ ? {
+ ...c,
+ vx: Math.max(-25, Math.min(25, vx)),
+ vy: Math.max(-25, Math.min(25, vy))
+ }
+ : c
+ )
+ );
+ } else {
+ soundEngine.drop(settings.soundEnabled);
+ }
+ };
+
+ // Keyboard Shortcuts (S, G, M, P, N, Space)
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ // Ignore if user is inside an input/textarea
+ if (['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement).tagName)) return;
+
+ if (e.key === 's' || e.key === 'S' || e.code === 'Space') {
+ e.preventDefault();
+ handleShuffle();
+ } else if (e.key === 'g' || e.key === 'G') {
+ e.preventDefault();
+ setSettings((prev) => ({
+ ...prev,
+ layoutMode: prev.layoutMode === 'scatter' ? 'grid' : 'scatter'
+ }));
+ } else if (e.key === 'p' || e.key === 'P') {
+ e.preventDefault();
+ setSettings((prev) => ({ ...prev, penMode: !prev.penMode }));
+ } else if (e.key === 'n' || e.key === 'N') {
+ e.preventDefault();
+ setIsAddOpen(true);
+ } else if (e.key === 'm' || e.key === 'M') {
+ e.preventDefault();
+ handleShuffle();
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [handleShuffle]);
+
+ // Load Presets
+ const handleLoadPreset = (presetName: string) => {
+ const w = window.innerWidth;
+ const h = window.innerHeight;
+
+ if (presetName === 'original') {
+ const generated = generateProceduralCards(20, w, h, 0.6, 'mixed');
+ setCards(generated);
+ setSettings((s) => ({ ...s, deskTexture: 'oak', lightAngle: 225, grain: 0.7 }));
+ } else if (presetName === 'designer') {
+ const designerCards: CardItem[] = SAMPLE_PRESETS.slice(0, 14).map((p, i) => ({
+ id: `design-${i}`,
+ type: p.type || 'sticky',
+ title: p.title,
+ content: p.content || '',
+ color: p.color || '#FFF9A6',
+ x: 140 + (i % 4) * 260,
+ y: 120 + Math.floor(i / 4) * 230,
+ vx: 0,
+ vy: 0,
+ rotation: (Math.random() - 0.5) * 6,
+ width: p.width || 180,
+ height: p.height || 180,
+ zIndex: i + 1,
+ isPinned: false,
+ attachType: i % 2 === 0 ? 'washi' : 'paperclip',
+ fontFamily: p.fontFamily || 'font-caveat',
+ todoItems: p.todoItems
+ }));
+ setCards(designerCards);
+ setSettings((s) => ({ ...s, deskTexture: 'cutting-mat', lightAngle: 180 }));
+ } else if (presetName === 'creative-coder') {
+ const coderCards: CardItem[] = [
+ {
+ id: 'code-1',
+ type: 'graph',
+ title: 'Sine wave vectors',
+ content: 'y = sin(x) * 45 + noise(x)',
+ graphType: 'curve',
+ x: 200,
+ y: 200,
+ vx: 0,
+ vy: 0,
+ rotation: -2,
+ width: 220,
+ height: 190,
+ zIndex: 1,
+ isPinned: false,
+ attachType: 'washi',
+ fontFamily: 'font-mono'
+ },
+ {
+ id: 'code-2',
+ type: 'torn',
+ title: 'Shader experiment',
+ subtitle: 'Fragment shader',
+ content: 'precision mediump float;\nuniform vec2 u_res;\nvoid main() {\n gl_FragColor = vec4(1.0);\n}',
+ x: 480,
+ y: 180,
+ vx: 0,
+ vy: 0,
+ rotation: 3,
+ width: 240,
+ height: 250,
+ zIndex: 2,
+ isPinned: false,
+ attachType: 'pin',
+ fontFamily: 'font-mono'
+ },
+ {
+ id: 'code-3',
+ type: 'sticky',
+ title: 'Pen Plotter Test',
+ content: 'AxiDraw v3\nSpeed: 30%\nPen: Sakura Pigma Micron 05',
+ color: '#FFF9A6',
+ x: 780,
+ y: 240,
+ vx: 0,
+ vy: 0,
+ rotation: -4,
+ width: 170,
+ height: 170,
+ zIndex: 3,
+ isPinned: false,
+ attachType: 'washi',
+ fontFamily: 'font-courier'
+ }
+ ];
+ setCards(coderCards);
+ setSettings((s) => ({ ...s, deskTexture: 'blueprint', lightAngle: 270 }));
+ } else if (presetName === 'photojournal') {
+ const photoCards = SAMPLE_PRESETS.filter((p) => p.type === 'polaroid').map((p, i) => ({
+ id: `photo-${i}`,
+ type: 'polaroid' as const,
+ title: p.title,
+ photoCaption: p.photoCaption,
+ content: p.content || '',
+ imageUrl: p.imageUrl,
+ x: 180 + (i % 3) * 280,
+ y: 140 + Math.floor(i / 3) * 260,
+ vx: 0,
+ vy: 0,
+ rotation: (Math.random() - 0.5) * 8,
+ width: 200,
+ height: 240,
+ zIndex: i + 1,
+ isPinned: false,
+ attachType: 'washi' as const,
+ fontFamily: 'font-caveat'
+ }));
+ setCards(photoCards);
+ setSettings((s) => ({ ...s, deskTexture: 'walnut', lightAngle: 225 }));
+ }
+ soundEngine.shuffle(settings.soundEnabled);
+ };
+
+ // Filtered Cards
+ const visibleCards = cards.filter((card) => {
+ if (settings.activeFilter === 'all') return true;
+ return card.type === settings.activeFilter;
+ });
+
+ return (
+
+ {/* 1. Procedural Desk Background Surface */}
+
+
+ {/* 2. Top Bar Controls */}
+
setIsAddOpen(true)}
+ onOpenPortfolio={() => setIsPortfolioOpen(true)}
+ totalCards={visibleCards.length}
+ />
+
+ {/* 3. Cards Layer */}
+
+ {visibleCards.map((card) => (
+
setEditingCard(c)}
+ onToggleTodo={(cardId, idx) => {
+ setCards((prev) =>
+ prev.map((c) => {
+ if (c.id !== cardId || !c.todoItems) return c;
+ const updatedTodos = [...c.todoItems];
+ updatedTodos[idx] = {
+ ...updatedTodos[idx],
+ done: !updatedTodos[idx].done
+ };
+ return { ...c, todoItems: updatedTodos };
+ })
+ );
+ }}
+ />
+ ))}
+
+
+ {/* 4. Stationery Props Layer (Pencils, Pens, Washi rolls, Paperclips) */}
+ {settings.showProps && (
+
+ )}
+
+ {/* 5. Pen / Freehand Ink Layer */}
+ setStrokes((prev) => [...prev, s])}
+ onClear={() => setStrokes([])}
+ />
+
+ {/* 6. Bottom Shortcut Bar */}
+
+
+ {/* 7. Modals */}
+ setIsAddOpen(false)}
+ onAdd={(newCard) => {
+ const w = window.innerWidth;
+ const h = window.innerHeight;
+ const created: CardItem = {
+ id: `card-custom-${Date.now()}`,
+ type: newCard.type || 'sticky',
+ title: newCard.title,
+ content: newCard.content || '',
+ imageUrl: newCard.imageUrl,
+ photoCaption: newCard.photoCaption,
+ color: newCard.color || '#FFF9A6',
+ x: w / 2 - 90 + (Math.random() - 0.5) * 60,
+ y: h / 2 - 90 + (Math.random() - 0.5) * 60,
+ vx: 0,
+ vy: 0,
+ rotation: (Math.random() - 0.5) * 8,
+ width: newCard.width || 180,
+ height: newCard.height || 180,
+ zIndex: maxZIndex + 1,
+ isPinned: false,
+ attachType: newCard.attachType || 'washi',
+ fontFamily: newCard.fontFamily || 'font-caveat'
+ };
+ setMaxZIndex(maxZIndex + 1);
+ setCards((prev) => [...prev, created]);
+ soundEngine.drop(settings.soundEnabled);
+ }}
+ />
+
+ setEditingCard(null)}
+ onSave={(updated) => {
+ setCards((prev) => prev.map((c) => (c.id === updated.id ? updated : c)));
+ soundEngine.rustle(settings.soundEnabled);
+ }}
+ onDelete={(id) => {
+ setCards((prev) => prev.filter((c) => c.id !== id));
+ soundEngine.rustle(settings.soundEnabled);
+ }}
+ />
+
+ setIsPortfolioOpen(false)}
+ cards={cards}
+ settings={settings}
+ onLoadPreset={handleLoadPreset}
+ onImportJson={(importedCards) => {
+ setCards(importedCards);
+ soundEngine.shuffle(settings.soundEnabled);
+ }}
+ />
+
+ );
+};
+
+export default App;
diff --git a/src/components/AddCardModal.tsx b/src/components/AddCardModal.tsx
new file mode 100644
index 0000000..979b3f7
--- /dev/null
+++ b/src/components/AddCardModal.tsx
@@ -0,0 +1,275 @@
+import React, { useState } from 'react';
+import { CardItem, CardType, AttachType } from '../types';
+import { X } from 'lucide-react';
+
+interface AddCardModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onAdd: (card: Partial) => void;
+}
+
+export const AddCardModal: React.FC = ({ isOpen, onClose, onAdd }) => {
+ const [type, setType] = useState('sticky');
+ const [title, setTitle] = useState('');
+ const [content, setContent] = useState('');
+ const [imageUrl, setImageUrl] = useState('');
+ const [color, setColor] = useState('#FFF9A6');
+ const [attachType, setAttachType] = useState('washi');
+ const [fontFamily, setFontFamily] = useState('font-caveat');
+
+ if (!isOpen) return null;
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ onAdd({
+ type,
+ title: title.trim() || undefined,
+ content: content.trim(),
+ imageUrl: type === 'polaroid' ? (imageUrl.trim() || 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=600&q=80') : undefined,
+ photoCaption: type === 'polaroid' ? (title.trim() || content.trim()) : undefined,
+ color,
+ attachType,
+ fontFamily,
+ width: type === 'polaroid' ? 175 : type === 'tag' ? 140 : 180,
+ height: type === 'polaroid' ? 220 : type === 'tag' ? 220 : 180
+ });
+ onClose();
+ setTitle('');
+ setContent('');
+ setImageUrl('');
+ };
+
+ return (
+
+
+
+
+ Add New Paper Card
+
+
+
+
+
+
+
+ );
+};
+
+interface EditCardModalProps {
+ card: CardItem | null;
+ isOpen: boolean;
+ onClose: () => void;
+ onSave: (updated: CardItem) => void;
+ onDelete: (id: string) => void;
+}
+
+export const EditCardModal: React.FC = ({
+ card,
+ isOpen,
+ onClose,
+ onSave,
+ onDelete
+}) => {
+ if (!isOpen || !card) return null;
+
+ const [title, setTitle] = useState(card.title || '');
+ const [content, setContent] = useState(card.content || '');
+ const [color, setColor] = useState(card.color || '#FFF9A6');
+
+ const handleSave = () => {
+ onSave({
+ ...card,
+ title: title.trim() || undefined,
+ content: content.trim(),
+ color
+ });
+ onClose();
+ };
+
+ return (
+
+
+
+
+ Edit Card ({card.type})
+
+
+
+
+
+
+
+ setTitle(e.target.value)}
+ className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500"
+ />
+
+
+
+
+
+
+
+
+
+ {['#FFF9A6', '#FFDFD3', '#E0D4FC', '#BEE7F5', '#C8F7C5', '#FFFFFF', '#282C34'].map((c) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/BottomBar.tsx b/src/components/BottomBar.tsx
new file mode 100644
index 0000000..88c0be9
--- /dev/null
+++ b/src/components/BottomBar.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+export const BottomBar: React.FC = () => {
+ return (
+
+ );
+};
diff --git a/src/components/DeskProps.tsx b/src/components/DeskProps.tsx
new file mode 100644
index 0000000..f0ed699
--- /dev/null
+++ b/src/components/DeskProps.tsx
@@ -0,0 +1,144 @@
+import React from 'react';
+import { DeskProp } from '../types';
+import { getPropShadow } from '../utils/lighting';
+
+interface DeskPropsProps {
+ props: DeskProp[];
+ lightAngle: number;
+ onPointerDown: (e: React.PointerEvent, prop: DeskProp) => void;
+}
+
+export const DeskPropsLayer: React.FC = ({
+ props,
+ lightAngle,
+ onPointerDown
+}) => {
+ return (
+
+ {props.map((prop) => {
+ const shadow = getPropShadow(lightAngle, 6);
+
+ return (
+
onPointerDown(e, prop)}
+ >
+ {/* 1. CLASSIC HB WOODEN PENCIL */}
+ {prop.type === 'pencil' && (
+
+ {/* Graphite Lead Tip */}
+
+ {/* Sharpened Wood Bevel */}
+
+ {/* Yellow Hexagonal Body */}
+
+
+ № 2 // HB
+
+
+ {/* Silver Metal Ferrule */}
+
+ {/* Pink Rubber Eraser */}
+
+
+ )}
+
+ {/* 2. BLACK GEL PEN */}
+ {prop.type === 'pen' && (
+
+ {/* Fine Metal Tip */}
+
+ {/* Pen Grip */}
+
+ {/* Matte Pen Body */}
+
+ {/* Metal Clip & Cap End */}
+
+
+ )}
+
+ {/* 3. HIGHLIGHTER / MARKER */}
+ {prop.type === 'marker' && (
+
+
+
+
+ CHISEL MARKER
+
+
+
+
+ )}
+
+ {/* 4. WASHI TAPE ROLL */}
+ {prop.type === 'tape-roll' && (
+
+ {/* Inner cardboard spool */}
+
+ {/* Washi edge tab */}
+
+
+ )}
+
+ {/* 5. LOOSE PAPERCLIP */}
+ {prop.type === 'paperclip' && (
+
+ )}
+
+ {/* 6. RED PUSHPIN */}
+ {prop.type === 'pin' && (
+
+ )}
+
+ );
+ })}
+
+ );
+};
diff --git a/src/components/DeskTextures.tsx b/src/components/DeskTextures.tsx
new file mode 100644
index 0000000..e3ac77c
--- /dev/null
+++ b/src/components/DeskTextures.tsx
@@ -0,0 +1,205 @@
+import React from 'react';
+import { DeskTexture } from '../types';
+
+interface DeskBackgroundProps {
+ texture: DeskTexture;
+ grain: number;
+}
+
+export const DeskBackground: React.FC = ({ texture, grain }) => {
+ return (
+ <>
+ {/* SVG Procedural Filter for Paper Fiber & Desk Grain */}
+
+
+ {/* Desk Surface Layer */}
+
+ {texture === 'oak' && (
+
+ )}
+
+ {texture === 'walnut' && (
+
+ )}
+
+ {texture === 'cutting-mat' && (
+
+ )}
+
+ {texture === 'corkboard' && (
+
+ )}
+
+ {texture === 'blueprint' && (
+
+ )}
+
+ {texture === 'dark-slate' && (
+
+ )}
+
+ {texture === 'marble' && (
+
+ )}
+
+ {/* Global procedural paper grain overlay */}
+
+
+ {/* Subtle vignette around viewport edges */}
+
+
+ >
+ );
+};
diff --git a/src/components/Fastener.tsx b/src/components/Fastener.tsx
new file mode 100644
index 0000000..49c0b24
--- /dev/null
+++ b/src/components/Fastener.tsx
@@ -0,0 +1,121 @@
+import React from 'react';
+import { AttachType, StampType } from '../types';
+import { getPinShadow, getTapeShadow } from '../utils/lighting';
+
+interface FastenerProps {
+ type: AttachType;
+ color?: string;
+ angle?: number;
+ lightAngle: number;
+}
+
+export const Fastener: React.FC = ({ type, color = '#E6C280', angle = -4, lightAngle }) => {
+ if (type === 'none') return null;
+
+ if (type === 'washi') {
+ return (
+
+
+ {/* Subtle tape sheen */}
+
+
+
+ );
+ }
+
+ if (type === 'pin') {
+ return (
+
+
+ {/* Pushpin specular highlight */}
+
+
+
+
+ );
+ }
+
+ if (type === 'paperclip') {
+ return (
+
+ );
+ }
+
+ return null;
+};
+
+export const Stamp: React.FC<{ type?: StampType; angle?: number }> = ({ type, angle = 8 }) => {
+ if (!type) return null;
+
+ const colorClass = type === 'APPROVED' ? 'text-red-700 border-red-700/80 bg-red-700/5' :
+ type === 'SAMPLE' ? 'text-amber-800 border-amber-800/80 bg-amber-800/5' :
+ type === 'PROOF' ? 'text-emerald-800 border-emerald-800/80 bg-emerald-800/5' :
+ type === 'DRAFT' ? 'text-blue-800 border-blue-800/80 bg-blue-800/5' :
+ 'text-neutral-800 border-neutral-800/80 bg-neutral-800/5';
+
+ return (
+
+ {type}
+
+ );
+};
+
+export const PhotoCorners: React.FC = () => {
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/src/components/PaperCard.tsx b/src/components/PaperCard.tsx
new file mode 100644
index 0000000..0ad3b1a
--- /dev/null
+++ b/src/components/PaperCard.tsx
@@ -0,0 +1,406 @@
+import React, { useState } from 'react';
+import { CardItem } from '../types';
+import { getShadowStyle } from '../utils/lighting';
+import { Fastener, Stamp, PhotoCorners } from './Fastener';
+
+interface PaperCardProps {
+ card: CardItem;
+ lightAngle: number;
+ isDragging: boolean;
+ onPointerDown: (e: React.PointerEvent, card: CardItem) => void;
+ onDoubleClick: (card: CardItem) => void;
+ onToggleTodo?: (cardId: string, index: number) => void;
+}
+
+export const PaperCard: React.FC = ({
+ card,
+ lightAngle,
+ isDragging,
+ onPointerDown,
+ onDoubleClick,
+ onToggleTodo
+}) => {
+ const [isHovered, setIsHovered] = useState(false);
+
+ // Get dynamic shadow based on light angle and drag state
+ const shadowStyle = getShadowStyle(
+ lightAngle,
+ isDragging,
+ isHovered ? 1.25 : 1.0,
+ card.type === 'sticky'
+ );
+
+ return (
+ onPointerDown(e, card)}
+ onDoubleClick={() => onDoubleClick(card)}
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+ {/* Top Attachment (Washi tape, pin, or paperclip) */}
+
+
+ {/* Main Paper Body */}
+
+ {/* Subtle Paper Grain Filter Overlay */}
+
+
+ {/* Paper Corner Curled Illusion for Sticky Notes */}
+ {card.type === 'sticky' && (
+
+ )}
+
+ {/* Rubber Stamp */}
+
+
+ {/* 1. POLAROID CARD */}
+ {card.type === 'polaroid' && (
+
+
+
+ {card.imageUrl ? (
+

+ ) : (
+
+ No Image
+
+ )}
+ {/* Glossy photo finish sheen */}
+
+
+
+
+
+ {card.photoCaption || card.title}
+
+
+
+ )}
+
+ {/* 2. STICKY NOTE */}
+ {card.type === 'sticky' && (
+
+ {card.title && (
+
+ {card.title}
+
+ )}
+
+ {card.todoItems && card.todoItems.length > 0 ? (
+
+ {card.todoItems.map((item, idx) => (
+
{
+ e.stopPropagation();
+ onToggleTodo?.(card.id, idx);
+ }}
+ >
+
+
+ {item.text}
+
+
+ ))}
+
+ ) : (
+
+ {card.content}
+
+ )}
+
+ {card.date && (
+
+ {card.date}
+
+ )}
+
+ )}
+
+ {/* 3. KRAFT LUGGAGE / PARCEL TAG */}
+ {card.type === 'tag' && (
+
+ {/* Tag string & brass eyelet */}
+
+
+
+
+ {card.tagNumber || '#1828'}
+
+
+ {card.subtitle || 'fragile'}
+
+
+ {card.date || 'Sept 4'}
+
+
+
+
+ PRIORITY PARCEL
+
+
+ )}
+
+ {/* 4. TORN PAPER / LETTER */}
+ {card.type === 'torn' && (
+
+
+
+ {card.title || 'Memo'}
+
+
+ {card.date || 'Fri — late'}
+
+
+
+ {card.subtitle && (
+
+ {card.subtitle}
+
+ )}
+
+ {card.todoItems && card.todoItems.length > 0 ? (
+
+ {card.todoItems.map((item, idx) => (
+
{
+ e.stopPropagation();
+ onToggleTodo?.(card.id, idx);
+ }}
+ >
+
+
+ {item.text}
+
+
+ ))}
+
+ ) : (
+
+ {card.content}
+
+ )}
+
+
+ NOTES // LOG
+ CONFIDENTIAL
+
+
+ )}
+
+ {/* 5. MINIMAL BUSINESS CARD */}
+ {card.type === 'business' && (
+
+
+ [ JL ]
+
+
+
+ {card.title || 'JULES LAURENT'}
+
+
+ {card.subtitle || 'PRODUCT DESIGNER'}
+
+
+
+ {card.content}
+
+
+ )}
+
+ {/* 6. STORE RECEIPT */}
+ {card.type === 'receipt' && (
+
+
+
{card.title || 'MALL STORE'}
+
{card.subtitle}
+
{card.date || '10/24 15:34'}
+
+
+
+ {card.receiptItems?.map((it, idx) => (
+
+ = (card.receiptItems?.length || 0) - 2 ? 'font-bold' : ''}>
+ {it.name}
+
+ {it.price}
+
+ ))}
+
+
+ {/* Barcode representation */}
+
+
+ {[2, 1, 3, 1, 2, 4, 1, 3, 2, 1, 3, 1, 2, 1, 4, 2, 1, 3, 2, 1].map((w, i) => (
+
+ ))}
+
+
+ * 8 4 9 2 0 1 8 5 *
+
+
+
+ )}
+
+ {/* 7. GRAPH / WIREFRAME SKETCH */}
+ {card.type === 'graph' && (
+
+
+
+ {card.title}
+
+ Fig. 1A
+
+
+ {/* SVG Hand-drawn Charts */}
+
+ {card.graphType === 'barchart' && (
+
+ )}
+
+ {card.graphType === 'flowchart' && (
+
+ )}
+
+ {card.graphType === 'curve' && (
+
+ )}
+
+
+
+ scale 1:1 · mm
+
+
+ )}
+
+ {/* 8. MOODBOARD CARD */}
+ {card.type === 'moodboard' && (
+
+
+
+ {card.title || 'Moodboard'}
+
+
+ {card.content}
+
+
+
+
+ {card.subtitle || 'Creative Direction'}
+ #mood
+
+
+ )}
+
+
+ );
+};
diff --git a/src/components/PenLayer.tsx b/src/components/PenLayer.tsx
new file mode 100644
index 0000000..17d7a21
--- /dev/null
+++ b/src/components/PenLayer.tsx
@@ -0,0 +1,149 @@
+import React, { useRef, useState, useEffect } from 'react';
+import { InkStroke } from '../types';
+
+interface PenLayerProps {
+ active: boolean;
+ strokes: InkStroke[];
+ onAddStroke: (stroke: InkStroke) => void;
+ onClear: () => void;
+}
+
+export const PenLayer: React.FC = ({
+ active,
+ strokes,
+ onAddStroke,
+ onClear
+}) => {
+ const canvasRef = useRef(null);
+ const [isDrawing, setIsDrawing] = useState(false);
+ const currentPoints = useRef<{ x: number; y: number }[]>([]);
+ const [currentColor, setCurrentColor] = useState('#1E293B'); // Ink color
+
+ // Redraw all strokes
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ // Resize canvas to window
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ strokes.forEach((stroke) => {
+ if (stroke.points.length < 2) return;
+ ctx.strokeStyle = stroke.color;
+ ctx.lineWidth = stroke.width;
+ ctx.beginPath();
+ ctx.moveTo(stroke.points[0].x, stroke.points[0].y);
+
+ for (let i = 1; i < stroke.points.length; i++) {
+ ctx.lineTo(stroke.points[i].x, stroke.points[i].y);
+ }
+ ctx.stroke();
+ });
+ }, [strokes]);
+
+ const handlePointerDown = (e: React.PointerEvent) => {
+ if (!active) return;
+ setIsDrawing(true);
+ currentPoints.current = [{ x: e.clientX, y: e.clientY }];
+ };
+
+ const handlePointerMove = (e: React.PointerEvent) => {
+ if (!active || !isDrawing) return;
+ const canvas = canvasRef.current;
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ const newPt = { x: e.clientX, y: e.clientY };
+ currentPoints.current.push(newPt);
+
+ ctx.strokeStyle = currentColor;
+ ctx.lineWidth = 2.2;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ const pts = currentPoints.current;
+ if (pts.length > 1) {
+ ctx.beginPath();
+ ctx.moveTo(pts[pts.length - 2].x, pts[pts.length - 2].y);
+ ctx.lineTo(newPt.x, newPt.y);
+ ctx.stroke();
+ }
+ };
+
+ const handlePointerUp = () => {
+ if (!active || !isDrawing) return;
+ setIsDrawing(false);
+ if (currentPoints.current.length > 1) {
+ onAddStroke({
+ id: `stroke-${Date.now()}`,
+ points: [...currentPoints.current],
+ color: currentColor,
+ width: 2.2
+ });
+ }
+ currentPoints.current = [];
+ };
+
+ if (!active) {
+ // Show static rendered canvas layer if there are strokes, but without mouse interception
+ return (
+
+ );
+ }
+
+ return (
+ <>
+
+
+ {/* Floating mini toolbar for ink color & clear */}
+
+ Ink:
+
+ >
+ );
+};
diff --git a/src/components/PortfolioModal.tsx b/src/components/PortfolioModal.tsx
new file mode 100644
index 0000000..98500b6
--- /dev/null
+++ b/src/components/PortfolioModal.tsx
@@ -0,0 +1,189 @@
+import React, { useState } from 'react';
+import { X, Copy, Check, Download, Upload, Sparkles } from 'lucide-react';
+import { CardItem, DeskSettings } from '../types';
+
+interface PortfolioModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ cards: CardItem[];
+ settings: DeskSettings;
+ onLoadPreset: (presetName: string) => void;
+ onImportJson: (jsonCards: CardItem[]) => void;
+}
+
+export const PortfolioModal: React.FC = ({
+ isOpen,
+ onClose,
+ cards,
+ settings,
+ onLoadPreset,
+ onImportJson
+}) => {
+ const [copied, setCopied] = useState(false);
+
+ if (!isOpen) return null;
+
+ const exportJson = () => {
+ const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(cards, null, 2));
+ const downloadAnchor = document.createElement('a');
+ downloadAnchor.setAttribute('href', dataStr);
+ downloadAnchor.setAttribute('download', 'paper-desk-layout.json');
+ document.body.appendChild(downloadAnchor);
+ downloadAnchor.click();
+ downloadAnchor.remove();
+ };
+
+ const handleCopyEmbed = () => {
+ const embedCode = ``;
+ navigator.clipboard.writeText(embedCode);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ };
+
+ const handleFileUpload = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const json = JSON.parse(event.target?.result as string);
+ if (Array.isArray(json)) {
+ onImportJson(json);
+ onClose();
+ }
+ } catch (err) {
+ alert('Invalid JSON file format');
+ }
+ };
+ reader.readAsText(file);
+ };
+
+ return (
+
+
+
+
+
+
+ Use as Portfolio & Embed System
+
+
+ Turn your desk into an interactive digital portfolio, moodboard, or personal archive.
+
+
+
+
+
+
+
+ {/* Preset Layouts */}
+
+
+ Curated Presets
+
+
+
{
+ onLoadPreset('original');
+ onClose();
+ }}
+ className="p-3 rounded-lg bg-neutral-800/80 hover:bg-neutral-800 border border-neutral-700 hover:border-amber-500/50 text-left transition"
+ >
+ Paper Desk (Original)
+
+ Faithful replica of Maksim Surguy's desk with notes, photos, receipts, and charts.
+
+
+
+
{
+ onLoadPreset('designer');
+ onClose();
+ }}
+ className="p-3 rounded-lg bg-neutral-800/80 hover:bg-neutral-800 border border-neutral-700 hover:border-amber-500/50 text-left transition"
+ >
+ Product Designer
+
+ Sprint tasks, wireframes, user testing stickies, design system specimens, and tags.
+
+
+
+
{
+ onLoadPreset('creative-coder');
+ onClose();
+ }}
+ className="p-3 rounded-lg bg-neutral-800/80 hover:bg-neutral-800 border border-neutral-700 hover:border-amber-500/50 text-left transition"
+ >
+ Creative Technologist
+
+ Generative shaders, pen plotter formulas, code snippets, and SVG filter experiments.
+
+
+
+
{
+ onLoadPreset('photojournal');
+ onClose();
+ }}
+ className="p-3 rounded-lg bg-neutral-800/80 hover:bg-neutral-800 border border-neutral-700 hover:border-amber-500/50 text-left transition"
+ >
+ Photographer Journal
+
+ High-density polaroids, photo corners, travel tickets, café receipts, and notes.
+
+
+
+
+
+ {/* Share & Embed */}
+
+
+ Embed on Your Website
+
+
+ `}
+ className="w-full bg-neutral-900 border border-neutral-700 rounded px-3 py-1.5 font-mono text-[11px] text-neutral-300"
+ />
+
+ {copied ? : }
+ {copied ? 'Copied!' : 'Copy Code'}
+
+
+
+
+ {/* Import & Export */}
+
+
+
+
+ Export Layout JSON
+
+
+
+
+
+
+ Done
+
+
+
+
+ );
+};
diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx
new file mode 100644
index 0000000..c71eef9
--- /dev/null
+++ b/src/components/TopBar.tsx
@@ -0,0 +1,284 @@
+import React from 'react';
+import { DeskSettings, DeskTexture, FontMode, CardType } from '../types';
+import { Volume2, VolumeX, Plus, Sparkles } from 'lucide-react';
+
+interface TopBarProps {
+ settings: DeskSettings;
+ onUpdateSettings: (newSettings: Partial) => void;
+ onShuffle: () => void;
+ onReplay: () => void;
+ onAddCard: () => void;
+ onOpenPortfolio: () => void;
+ totalCards: number;
+}
+
+export const TopBar: React.FC = ({
+ settings,
+ onUpdateSettings,
+ onShuffle,
+ onReplay,
+ onAddCard,
+ onOpenPortfolio,
+ totalCards
+}) => {
+ return (
+
+ {/* 1. Left: Brand Title & Subtitle */}
+
+
+
+
+ Glimpse
+
+
+
+
+ procedural paper · visual desk
+
+
+
+
+ {/* 2. Primary Actions: Shuffle, Scatter/Grid */}
+
+
+ Shuffle
+
+
+ {/* Scatter / Grid Toggle */}
+
+ onUpdateSettings({ layoutMode: 'scatter' })}
+ className={`px-2 py-0.5 rounded transition ${
+ settings.layoutMode === 'scatter'
+ ? 'bg-neutral-600 text-white font-medium shadow-xs'
+ : 'text-neutral-400 hover:text-neutral-200'
+ }`}
+ title="Press 'G' to toggle"
+ >
+ Scatter
+
+ onUpdateSettings({ layoutMode: 'grid' })}
+ className={`px-2 py-0.5 rounded transition ${
+ settings.layoutMode === 'grid'
+ ? 'bg-neutral-600 text-white font-medium shadow-xs'
+ : 'text-neutral-400 hover:text-neutral-200'
+ }`}
+ title="Press 'G' to toggle"
+ >
+ Grid
+
+
+
+ {/* Typography Mode: Mixed, Handwritten, Typed */}
+
+ {(['mixed', 'handwritten', 'typed'] as FontMode[]).map((mode) => (
+ onUpdateSettings({ fontMode: mode })}
+ className={`px-2 py-0.5 rounded capitalize transition ${
+ settings.fontMode === mode
+ ? 'bg-neutral-600 text-white font-medium shadow-xs'
+ : 'text-neutral-400 hover:text-neutral-200'
+ }`}
+ >
+ {mode}
+
+ ))}
+
+
+
+ {/* 3. Desk Texture Selector */}
+
+ Desk:
+
+
+
+ {/* 4. Sliders: Cards, Grain, Attach, Light */}
+
+
+ {/* 5. Checkboxes: Props, Motion, Pen */}
+
+
+
+
+
+
+
+
+ {/* 6. Action Buttons & Filter */}
+
+
+ Replay
+
+
+ {/* Sound Toggle */}
+
onUpdateSettings({ soundEnabled: !settings.soundEnabled })}
+ className={`px-2 py-0.5 rounded border transition flex items-center gap-1 ${
+ settings.soundEnabled
+ ? 'bg-neutral-800 text-amber-400 border-amber-600/50'
+ : 'bg-neutral-800/60 text-neutral-400 border-neutral-700'
+ }`}
+ title="Toggle paper & stationery audio effects"
+ >
+ {settings.soundEnabled ? : }
+ {settings.soundEnabled ? 'Sound on' : 'Sound off'}
+
+
+ {/* Card Type Filter */}
+
+
+ {/* Add New Card (+) Button */}
+
+
+
+
+ {/* Portfolio Mode / Presets Modal Button */}
+
+
+ Portfolio
+
+
+ {/* Stats readout like in original */}
+
+ {totalCards}/{totalCards} sheets
+
+
+
+ );
+};
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..43b8588
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,61 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer utilities {
+ .font-caveat {
+ font-family: 'Caveat', cursive;
+ }
+ .font-reenie {
+ font-family: 'Reenie Beanie', cursive;
+ }
+ .font-indie {
+ font-family: 'Indie Flower', cursive;
+ }
+ .font-shadows {
+ font-family: 'Shadows Into Light', cursive;
+ }
+ .font-courier {
+ font-family: 'Courier Prime', Courier, monospace;
+ }
+ .font-special {
+ font-family: 'Special Elite', monospace;
+ }
+ .font-mono {
+ font-family: 'JetBrains Mono', monospace;
+ }
+}
+
+html, body, #root {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ user-select: none;
+ -webkit-user-select: none;
+ -webkit-touch-callout: none;
+}
+
+/* Custom range sliders styling */
+input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: #f59e0b;
+ cursor: pointer;
+ border: 1px solid #78350f;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.5);
+}
+
+input[type="range"]::-moz-range-thumb {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: #f59e0b;
+ cursor: pointer;
+ border: 1px solid #78350f;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.5);
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..2339d59
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import './index.css';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..8b918d9
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,91 @@
+export type CardType =
+ | 'sticky'
+ | 'polaroid'
+ | 'tag'
+ | 'torn'
+ | 'business'
+ | 'receipt'
+ | 'graph'
+ | 'moodboard';
+
+export type AttachType = 'none' | 'washi' | 'paperclip' | 'pin';
+
+export type DeskTexture =
+ | 'oak'
+ | 'walnut'
+ | 'cutting-mat'
+ | 'corkboard'
+ | 'blueprint'
+ | 'dark-slate'
+ | 'marble';
+
+export type FontMode = 'mixed' | 'handwritten' | 'typed';
+
+export type StampType = 'SAMPLE' | 'DRAFT' | 'PROOF' | 'APPROVED' | 'KEEP' | 'CONFIDENTIAL';
+
+export interface CardItem {
+ id: string;
+ type: CardType;
+ title?: string;
+ content: string;
+ subtitle?: string;
+ date?: string;
+ author?: string;
+ imageUrl?: string;
+ photoCaption?: string;
+ color?: string; // pastel hex or theme
+ x: number;
+ y: number;
+ vx: number;
+ vy: number;
+ rotation: number;
+ targetRotation?: number;
+ width: number;
+ height: number;
+ zIndex: number;
+ isPinned: boolean;
+ attachType: AttachType;
+ attachColor?: string;
+ attachAngle?: number;
+ stamp?: StampType;
+ stampAngle?: number;
+ fontFamily: string;
+ todoItems?: { text: string; done: boolean }[];
+ graphType?: 'barchart' | 'flowchart' | 'wireframe' | 'curve';
+ receiptItems?: { name: string; price: string }[];
+ tagNumber?: string;
+ aspectRatio?: number;
+}
+
+export interface DeskProp {
+ id: string;
+ type: 'pencil' | 'pen' | 'marker' | 'paperclip' | 'pin' | 'tape-roll';
+ x: number;
+ y: number;
+ rotation: number;
+ color?: string;
+ scale?: number;
+ zIndex: number;
+}
+
+export interface InkStroke {
+ id: string;
+ points: { x: number; y: number }[];
+ color: string;
+ width: number;
+}
+
+export interface DeskSettings {
+ lightAngle: number; // 0 - 360
+ grain: number; // 0 - 1
+ attachChance: number; // 0 - 1
+ cardCount: number; // 10 - 80
+ deskTexture: DeskTexture;
+ layoutMode: 'scatter' | 'grid';
+ fontMode: FontMode;
+ showProps: boolean;
+ enableMotion: boolean;
+ penMode: boolean;
+ soundEnabled: boolean;
+ activeFilter: 'all' | CardType;
+}
diff --git a/src/utils/lighting.ts b/src/utils/lighting.ts
new file mode 100644
index 0000000..5ed93d2
--- /dev/null
+++ b/src/utils/lighting.ts
@@ -0,0 +1,90 @@
+// Dynamic directional lighting and shadow calculation
+
+export function getShadowStyle(
+ lightAngleDeg: number,
+ isDragging: boolean = false,
+ elevation: number = 1.0,
+ isCurled: boolean = false
+): {
+ boxShadow: string;
+ transformStyle?: React.CSSProperties;
+} {
+ // Light shines from lightAngleDeg, shadow casts toward opposite angle
+ const shadowAngleRad = ((lightAngleDeg + 180) % 360) * (Math.PI / 180);
+
+ const cos = Math.cos(shadowAngleRad);
+ const sin = Math.sin(shadowAngleRad);
+
+ if (isDragging) {
+ // Lifted high in the air
+ const dist1 = 18 * elevation;
+ const dist2 = 36 * elevation;
+ const x1 = Math.round(cos * dist1);
+ const y1 = Math.round(sin * dist1);
+ const x2 = Math.round(cos * dist2);
+ const y2 = Math.round(sin * dist2);
+
+ return {
+ boxShadow: `
+ ${x1}px ${y1}px 24px rgba(0, 0, 0, 0.18),
+ ${x2}px ${y2}px 48px rgba(0, 0, 0, 0.14),
+ 0 0 1px rgba(0, 0, 0, 0.25)
+ `.trim()
+ };
+ }
+
+ // Resting on the desk
+ const distContact = 3 * elevation;
+ const distAmbient = 8 * elevation;
+ const distDiffuse = 16 * elevation;
+
+ const cx = Math.round(cos * distContact);
+ const cy = Math.round(sin * distContact);
+
+ const ax = Math.round(cos * distAmbient);
+ const ay = Math.round(sin * distAmbient);
+
+ const dx = Math.round(cos * distDiffuse);
+ const dy = Math.round(sin * distDiffuse);
+
+ // If curled, add asymmetric bottom shadow
+ const curlExtra = isCurled
+ ? `, ${Math.round(cos * 10 + 3)}px ${Math.round(sin * 12 + 6)}px 14px rgba(0, 0, 0, 0.12)`
+ : '';
+
+ return {
+ boxShadow: `
+ ${cx}px ${cy}px 4px rgba(0, 0, 0, 0.10),
+ ${ax}px ${ay}px 12px rgba(0, 0, 0, 0.08),
+ ${dx}px ${dy}px 24px rgba(0, 0, 0, 0.05)
+ ${curlExtra}
+ `.trim()
+ };
+}
+
+export function getTapeShadow(lightAngleDeg: number) {
+ const shadowAngleRad = ((lightAngleDeg + 180) % 360) * (Math.PI / 180);
+ const cos = Math.cos(shadowAngleRad);
+ const sin = Math.sin(shadowAngleRad);
+ const x = Math.round(cos * 2);
+ const y = Math.round(sin * 2);
+ return `${x}px ${y}px 3px rgba(0,0,0,0.18)`;
+}
+
+export function getPinShadow(lightAngleDeg: number) {
+ const shadowAngleRad = ((lightAngleDeg + 180) % 360) * (Math.PI / 180);
+ const cos = Math.cos(shadowAngleRad);
+ const sin = Math.sin(shadowAngleRad);
+ const x = Math.round(cos * 6);
+ const y = Math.round(sin * 6);
+ return `${x}px ${y}px 8px rgba(0,0,0,0.35)`;
+}
+
+export function getPropShadow(lightAngleDeg: number, height = 8) {
+ const shadowAngleRad = ((lightAngleDeg + 180) % 360) * (Math.PI / 180);
+ const cos = Math.cos(shadowAngleRad);
+ const sin = Math.sin(shadowAngleRad);
+ const x = Math.round(cos * height);
+ const y = Math.round(sin * height);
+ return `${x}px ${y}px ${Math.round(height * 1.5)}px rgba(0,0,0,0.25)`;
+}
diff --git a/src/utils/proceduralCards.ts b/src/utils/proceduralCards.ts
new file mode 100644
index 0000000..42101ca
--- /dev/null
+++ b/src/utils/proceduralCards.ts
@@ -0,0 +1,366 @@
+import { CardItem, CardType, StampType } from '../types';
+
+// Authentic seed data inspired directly by Paper Desk
+export const SAMPLE_PRESETS: Partial[] = [
+ {
+ type: 'moodboard',
+ title: 'Moodboard',
+ content: 'ship it on Friday\nremember: kerning on the headline\nthank-you notes for the team',
+ color: '#FAF8F5',
+ fontFamily: 'font-caveat',
+ width: 250,
+ height: 190,
+ stamp: 'SAMPLE',
+ stampAngle: -4
+ },
+ {
+ type: 'sticky',
+ content: 'what if the\ngrid was 5\ncolumns?',
+ title: 'Moodboard',
+ color: '#E0D4FC', // lavender sticky
+ fontFamily: 'font-caveat',
+ width: 155,
+ height: 155,
+ rotation: 1.5
+ },
+ {
+ type: 'sticky',
+ title: 'idea: paper-textured hero',
+ content: 'idea: paper-\ntextured\nhero',
+ color: '#FFDE9E', // warm peach/orange sticky
+ fontFamily: 'font-caveat',
+ width: 170,
+ height: 170,
+ rotation: -1
+ },
+ {
+ type: 'tag',
+ title: 'No. 1828',
+ subtitle: 'fragile',
+ date: 'Sept 4',
+ content: 'PARCEL TAG',
+ color: '#CBB296', // kraft luggage tag
+ fontFamily: 'font-courier',
+ width: 130,
+ height: 220,
+ rotation: -7,
+ tagNumber: '#1828'
+ },
+ {
+ type: 'business',
+ title: 'JULES LAURENT',
+ subtitle: 'PRODUCT DESIGNER',
+ content: 'studio@juleslaurent.design\nportfolio / archive',
+ color: '#BFA89B', // muted mauve/kraft cardstock
+ fontFamily: 'font-courier',
+ width: 210,
+ height: 125,
+ rotation: -2
+ },
+ {
+ type: 'sticky',
+ title: 'Keep',
+ content: 'Keep',
+ color: '#282C34', // dark matte card
+ fontFamily: 'font-reenie',
+ width: 170,
+ height: 120,
+ rotation: -8
+ },
+ {
+ type: 'torn',
+ title: 'Fri — late',
+ subtitle: 'Dear Ava,',
+ content: 'Less is more. Rename the project. Also: try warmer whites, and sketch 20 ideas. Keep 3.\n\nMore soon —',
+ color: '#E8DCC8', // aged stationary paper
+ fontFamily: 'font-special',
+ width: 240,
+ height: 270,
+ rotation: 0.5
+ },
+ {
+ type: 'polaroid',
+ title: 'DAY!',
+ photoCaption: 'DAY!',
+ content: 'Sunny afternoon on the coast',
+ imageUrl: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=600&q=80',
+ color: '#FFFFFE',
+ fontFamily: 'font-special',
+ width: 160,
+ height: 200,
+ rotation: 2
+ },
+ {
+ type: 'polaroid',
+ title: 'summit jump',
+ photoCaption: 'summit jump',
+ content: 'High Sierra trail',
+ imageUrl: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&q=80',
+ color: '#FFFFFE',
+ fontFamily: 'font-caveat',
+ width: 220,
+ height: 210,
+ rotation: -3
+ },
+ {
+ type: 'polaroid',
+ title: 'so many towels',
+ photoCaption: 'so many towels\nSept 1',
+ content: 'Mediterranean beach',
+ imageUrl: 'https://images.unsplash.com/photo-1519046904884-53103b34b206?auto=format&fit=crop&w=600&q=80',
+ color: '#F4EFEA',
+ fontFamily: 'font-indie',
+ width: 175,
+ height: 230,
+ rotation: -4
+ },
+ {
+ type: 'sticky',
+ title: 'Reading list',
+ content: 'Weekly chores & reading',
+ color: '#BEE7F5', // pastel sky blue
+ fontFamily: 'font-caveat',
+ width: 175,
+ height: 205,
+ rotation: 1,
+ todoItems: [
+ { text: 'draft blog post', done: true },
+ { text: 'order business cards', done: false },
+ { text: 'water plants', done: true },
+ { text: 'bread', done: false }
+ ]
+ },
+ {
+ type: 'torn',
+ title: 'Sprint 12',
+ content: 'Sprint tasks',
+ color: '#FFFDF9',
+ fontFamily: 'font-caveat',
+ width: 165,
+ height: 280,
+ rotation: -5,
+ todoItems: [
+ { text: 'wireframes', done: false },
+ { text: 'email client', done: false },
+ { text: 'photograph sketches', done: true },
+ { text: 'review PR', done: false },
+ { text: 'draft blog post', done: true },
+ { text: 'order business cards', done: true },
+ { text: 'fix nav on mobile', done: false },
+ { text: 'export assets', done: false }
+ ]
+ },
+ {
+ type: 'graph',
+ title: 'User growth & metrics',
+ content: 'Q3 Analytics trajectory',
+ color: '#F0F2F5',
+ fontFamily: 'font-courier',
+ width: 200,
+ height: 200,
+ graphType: 'barchart',
+ rotation: 3
+ },
+ {
+ type: 'graph',
+ title: 'Architecture flowchart',
+ content: 'System wireframe blocks',
+ color: '#F8F6F0',
+ fontFamily: 'font-courier',
+ width: 220,
+ height: 190,
+ graphType: 'flowchart',
+ rotation: -2
+ },
+ {
+ type: 'graph',
+ title: 'Coordinate wireframe',
+ content: 'Grid layout specs',
+ color: '#FCFAF6',
+ fontFamily: 'font-courier',
+ width: 210,
+ height: 180,
+ graphType: 'curve',
+ rotation: 2
+ },
+ {
+ type: 'receipt',
+ title: 'MALL STORE',
+ subtitle: 'COFFEE & STATIONERY',
+ date: '10/24 15:34',
+ content: 'Receipt #9482',
+ color: '#F9F8F4',
+ fontFamily: 'font-mono',
+ width: 155,
+ height: 310,
+ rotation: 4,
+ receiptItems: [
+ { name: 'PAPERBACK', price: '14.99' },
+ { name: 'FLAT WHITE', price: '5.25' },
+ { name: 'BAGEL', price: '4.50' },
+ { name: 'WASHI TAPE', price: '3.75' },
+ { name: 'SUBTOTAL', price: '28.49' },
+ { name: 'TAX', price: '2.56' },
+ { name: 'TOTAL', price: '$31.05' }
+ ]
+ },
+ {
+ type: 'moodboard',
+ title: 'Q3 goals',
+ content: 'idea: paper-textured hero\ncoffee w/ Sam — 3pm\nTuesday: user interviews\nexplore SVG fractal noise filters',
+ color: '#FFF8E8',
+ fontFamily: 'font-indie',
+ width: 240,
+ height: 190,
+ rotation: -1.5,
+ stamp: 'APPROVED',
+ stampAngle: 12
+ },
+ {
+ type: 'sticky',
+ title: 'Moodboard',
+ content: 'remember:\nkerning on the\nhero typography',
+ color: '#FFD39B',
+ fontFamily: 'font-caveat',
+ width: 160,
+ height: 160,
+ rotation: 3
+ },
+ {
+ type: 'polaroid',
+ title: 'City Walk',
+ photoCaption: 'by Monday',
+ content: 'Downtown reflections',
+ imageUrl: 'https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=600&q=80',
+ color: '#FFFFFF',
+ fontFamily: 'font-caveat',
+ width: 170,
+ height: 210,
+ rotation: -6
+ },
+ {
+ type: 'polaroid',
+ title: 'Desert Dunes',
+ photoCaption: 'golden hour',
+ content: 'Warm light on ridges',
+ imageUrl: 'https://images.unsplash.com/photo-1509316975850-ff9c5deb0cd9?auto=format&fit=crop&w=600&q=80',
+ color: '#FFFFFF',
+ fontFamily: 'font-indie',
+ width: 180,
+ height: 215,
+ rotation: 4
+ }
+];
+
+const WASHI_COLORS = [
+ '#E6C280', // kraft masking tape
+ '#8FB39A', // sage green washi
+ '#D98880', // coral pink washi
+ '#85C1E9', // sky blue washi
+ '#D7BDE2', // lavender washi
+ '#F7DC6F', // pale gold washi
+ '#48C9B0' // mint washi
+];
+
+const STICKY_COLORS = [
+ '#FFF9A6', // classic post-it yellow
+ '#FFDFD3', // pastel peach
+ '#E0D4FC', // lavender
+ '#BEE7F5', // pastel sky blue
+ '#C8F7C5', // soft mint
+ '#FFD6E0', // soft blush pink
+ '#FFE2CC' // warm cream
+];
+
+const FONTS_BY_MODE = {
+ handwritten: ['font-caveat', 'font-reenie', 'font-indie', 'font-shadows'],
+ typed: ['font-courier', 'font-special', 'font-mono'],
+ mixed: ['font-caveat', 'font-courier', 'font-reenie', 'font-special', 'font-indie', 'font-mono']
+};
+
+export function generateProceduralCards(
+ count: number,
+ canvasWidth: number,
+ canvasHeight: number,
+ attachChance: number = 0.55,
+ fontMode: 'mixed' | 'handwritten' | 'typed' = 'mixed'
+): CardItem[] {
+ const cards: CardItem[] = [];
+ const paddingX = 80;
+ const paddingY = 90;
+ const usableWidth = Math.max(500, canvasWidth - paddingX * 2);
+ const usableHeight = Math.max(400, canvasHeight - paddingY * 2);
+
+ const availableFonts = FONTS_BY_MODE[fontMode];
+
+ for (let i = 0; i < count; i++) {
+ const preset = SAMPLE_PRESETS[i % SAMPLE_PRESETS.length];
+
+ // Position organically across desk with slight cluster jitter
+ const gridCols = Math.ceil(Math.sqrt(count * 1.5));
+ const gridRows = Math.ceil(count / gridCols);
+ const col = i % gridCols;
+ const row = Math.floor(i / gridCols);
+
+ const cellW = usableWidth / gridCols;
+ const cellH = usableHeight / gridRows;
+
+ // Base position with organic jitter
+ const baseX = paddingX + col * cellW + (Math.random() - 0.5) * cellW * 0.7;
+ const baseY = paddingY + row * cellH + (Math.random() - 0.5) * cellH * 0.7;
+
+ // Attachments (tape, clip, pin)
+ let attachType: CardItem['attachType'] = 'none';
+ if (Math.random() < attachChance) {
+ const rand = Math.random();
+ if (rand < 0.45) attachType = 'washi';
+ else if (rand < 0.75) attachType = 'pin';
+ else attachType = 'paperclip';
+ }
+
+ // Stamps
+ let stamp: StampType | undefined = preset.stamp;
+ if (!stamp && Math.random() < 0.15) {
+ const stamps: StampType[] = ['SAMPLE', 'DRAFT', 'PROOF', 'APPROVED'];
+ stamp = stamps[Math.floor(Math.random() * stamps.length)];
+ }
+
+ const rotation = (preset.rotation ?? 0) + (Math.random() * 10 - 5);
+
+ const chosenFont = availableFonts[Math.floor(Math.random() * availableFonts.length)];
+
+ cards.push({
+ id: `card-${i}-${Date.now().toString(36)}`,
+ type: preset.type || 'sticky',
+ title: preset.title,
+ subtitle: preset.subtitle,
+ content: preset.content || '',
+ date: preset.date,
+ author: preset.author,
+ imageUrl: preset.imageUrl,
+ photoCaption: preset.photoCaption,
+ color: preset.color || STICKY_COLORS[Math.floor(Math.random() * STICKY_COLORS.length)],
+ x: Math.max(paddingX, Math.min(canvasWidth - 250, baseX)),
+ y: Math.max(paddingY, Math.min(canvasHeight - 250, baseY)),
+ vx: 0,
+ vy: 0,
+ rotation: rotation,
+ width: preset.width || 180,
+ height: preset.height || 180,
+ zIndex: i + 1,
+ isPinned: false,
+ attachType: attachType,
+ attachColor: WASHI_COLORS[Math.floor(Math.random() * WASHI_COLORS.length)],
+ attachAngle: Math.floor(Math.random() * 20 - 10),
+ stamp: stamp,
+ stampAngle: Math.floor(Math.random() * 24 - 12),
+ fontFamily: chosenFont,
+ todoItems: preset.todoItems ? JSON.parse(JSON.stringify(preset.todoItems)) : undefined,
+ graphType: preset.graphType,
+ receiptItems: preset.receiptItems,
+ tagNumber: preset.tagNumber
+ });
+ }
+
+ return cards;
+}
diff --git a/src/utils/sound.ts b/src/utils/sound.ts
new file mode 100644
index 0000000..6db39c8
--- /dev/null
+++ b/src/utils/sound.ts
@@ -0,0 +1,184 @@
+// Web Audio API Synthesizer for tactile paper & stationery sounds
+
+let audioCtx: AudioContext | null = null;
+
+function getAudioContext(): AudioContext | null {
+ if (typeof window === 'undefined') return null;
+ if (!audioCtx) {
+ const AudioContextClass = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
+ if (AudioContextClass) {
+ audioCtx = new AudioContextClass();
+ }
+ }
+ if (audioCtx && audioCtx.state === 'suspended') {
+ audioCtx.resume().catch(() => {});
+ }
+ return audioCtx;
+}
+
+export const soundEngine = {
+ // Paper sliding / rustle sound
+ rustle: (enabled = true, intensity = 1.0) => {
+ if (!enabled) return;
+ const ctx = getAudioContext();
+ if (!ctx) return;
+
+ try {
+ const bufferSize = ctx.sampleRate * 0.12; // 120ms
+ const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
+ const data = buffer.getChannelData(0);
+ for (let i = 0; i < bufferSize; i++) {
+ data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.45));
+ }
+
+ const noise = ctx.createBufferSource();
+ noise.buffer = buffer;
+
+ const filter = ctx.createBiquadFilter();
+ filter.type = 'bandpass';
+ filter.frequency.setValueAtTime(1400 + Math.random() * 400, ctx.currentTime);
+ filter.Q.setValueAtTime(1.8, ctx.currentTime);
+
+ const gain = ctx.createGain();
+ gain.gain.setValueAtTime(0.08 * intensity, ctx.currentTime);
+ gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.12);
+
+ noise.connect(filter);
+ filter.connect(gain);
+ gain.connect(ctx.destination);
+
+ noise.start();
+ } catch {
+ // Audio might be blocked before first interaction
+ }
+ },
+
+ // Paper landing / drop thud
+ drop: (enabled = true) => {
+ if (!enabled) return;
+ const ctx = getAudioContext();
+ if (!ctx) return;
+
+ try {
+ const now = ctx.currentTime;
+
+ // Low frequency soft thud
+ const osc = ctx.createOscillator();
+ const oscGain = ctx.createGain();
+ osc.type = 'sine';
+ osc.frequency.setValueAtTime(120, now);
+ osc.frequency.exponentialRampToValueAtTime(45, now + 0.08);
+
+ oscGain.gain.setValueAtTime(0.12, now);
+ oscGain.gain.exponentialRampToValueAtTime(0.001, now + 0.08);
+
+ osc.connect(oscGain);
+ oscGain.connect(ctx.destination);
+ osc.start(now);
+ osc.stop(now + 0.09);
+
+ // Light paper flap noise
+ soundEngine.rustle(enabled, 0.6);
+ } catch {}
+ },
+
+ // Flick / throw whoosh
+ flick: (enabled = true) => {
+ if (!enabled) return;
+ const ctx = getAudioContext();
+ if (!ctx) return;
+
+ try {
+ const now = ctx.currentTime;
+ const bufferSize = ctx.sampleRate * 0.18;
+ const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
+ const data = buffer.getChannelData(0);
+ for (let i = 0; i < bufferSize; i++) {
+ data[i] = (Math.random() * 2 - 1) * Math.sin((i / bufferSize) * Math.PI);
+ }
+
+ const noise = ctx.createBufferSource();
+ noise.buffer = buffer;
+
+ const filter = ctx.createBiquadFilter();
+ filter.type = 'lowpass';
+ filter.frequency.setValueAtTime(800, now);
+ filter.frequency.exponentialRampToValueAtTime(1800, now + 0.08);
+ filter.frequency.exponentialRampToValueAtTime(600, now + 0.18);
+
+ const gain = ctx.createGain();
+ gain.gain.setValueAtTime(0.09, now);
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.18);
+
+ noise.connect(filter);
+ filter.connect(gain);
+ gain.connect(ctx.destination);
+
+ noise.start(now);
+ } catch {}
+ },
+
+ // Pin stick / tap sound
+ pin: (enabled = true) => {
+ if (!enabled) return;
+ const ctx = getAudioContext();
+ if (!ctx) return;
+
+ try {
+ const now = ctx.currentTime;
+ const osc = ctx.createOscillator();
+ const gain = ctx.createGain();
+ osc.type = 'triangle';
+ osc.frequency.setValueAtTime(900, now);
+ osc.frequency.exponentialRampToValueAtTime(200, now + 0.05);
+
+ gain.gain.setValueAtTime(0.18, now);
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.05);
+
+ osc.connect(gain);
+ gain.connect(ctx.destination);
+ osc.start(now);
+ osc.stop(now + 0.06);
+ } catch {}
+ },
+
+ // Tape peel / stick sound
+ tape: (enabled = true) => {
+ if (!enabled) return;
+ const ctx = getAudioContext();
+ if (!ctx) return;
+
+ try {
+ const now = ctx.currentTime;
+ const osc = ctx.createOscillator();
+ const gain = ctx.createGain();
+ osc.type = 'sawtooth';
+ osc.frequency.setValueAtTime(3200, now);
+ osc.frequency.linearRampToValueAtTime(1600, now + 0.06);
+
+ gain.gain.setValueAtTime(0.04, now);
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.06);
+
+ const filter = ctx.createBiquadFilter();
+ filter.type = 'bandpass';
+ filter.frequency.setValueAtTime(2200, now);
+
+ osc.connect(filter);
+ filter.connect(gain);
+ gain.connect(ctx.destination);
+
+ osc.start(now);
+ osc.stop(now + 0.07);
+ } catch {}
+ },
+
+ // Shuffle multiple sheets rapidly
+ shuffle: (enabled = true) => {
+ if (!enabled) return;
+ for (let i = 0; i < 5; i++) {
+ setTimeout(() => {
+ soundEngine.rustle(enabled, 0.7 + Math.random() * 0.4);
+ }, i * 45);
+ }
+ }
+};
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..f1ea004
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,26 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ caveat: ['Caveat', 'cursive'],
+ reenie: ['"Reenie Beanie"', 'cursive'],
+ indie: ['"Indie Flower"', 'cursive'],
+ courier: ['"Courier Prime"', 'Courier', 'monospace'],
+ special: ['"Special Elite"', 'monospace'],
+ mono: ['"JetBrains Mono"', 'monospace'],
+ serif: ['"Playfair Display"', 'Georgia', 'serif'],
+ },
+ boxShadow: {
+ 'paper': '0 2px 8px rgba(0,0,0,0.12), 0 8px 24px rgba(0,0,0,0.08)',
+ 'lifted': '0 12px 32px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.12)',
+ 'pin': '0 4px 8px rgba(0,0,0,0.3)',
+ }
+ },
+ },
+ plugins: [],
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..1b6109f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..15e99ad
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 5173,
+ host: true
+ }
+})