From 278d47c7934c4f17b7f1c4466f09a849b6b8ca53 Mon Sep 17 00:00:00 2001 From: Ferdinand Urban Date: Fri, 24 Apr 2026 12:45:43 +0000 Subject: [PATCH] Initial commit: FuerDieCuts Vite/React app Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 5 + .vch-description | 1 + CLAUDE.md | 85 ++ README.md | 38 + index.html | 15 + package-lock.json | 1794 +++++++++++++++++++++++++++++++++++++++++++ package.json | 20 + src/App.jsx | 569 ++++++++++++++ src/TweaksPanel.jsx | 358 +++++++++ src/main.jsx | 9 + src/styles.css | 325 ++++++++ vite.config.js | 7 + 12 files changed, 3226 insertions(+) create mode 100644 .gitignore create mode 100644 .vch-description create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.jsx create mode 100644 src/TweaksPanel.jsx create mode 100644 src/main.jsx create mode 100644 src/styles.css create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..869e9d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +.env.local +.env.*.local diff --git a/.vch-description b/.vch-description new file mode 100644 index 0000000..9ddeeb3 --- /dev/null +++ b/.vch-description @@ -0,0 +1 @@ +A desktop tool for assembling weekly "Best-of" video reels from WhatsApp clips. Import clips, get automatic transcriptions via Whisper, review AI recommendations for each clip, select what goes into the reel, and reorder via drag-and-drop before exporting with ffmpeg. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b1a0791 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,85 @@ +# Project + +## Environment + +You are working inside a VCH cloud development environment. + +- **OS:** Debian-based LXC container +- **Editor:** code-server (VS Code in browser) on port 8080 +- **CLI:** Claude Code is available as `claude` in terminal +- **Git:** Pre-configured. Gitea integration available through VCH. +- **Preview:** Start a dev server on any port — VCH detects listening ports automatically and provides preview URLs with SSL. +- **Publishing:** Assign a public subdomain to any port via the VCH Publish page (automatic SSL). +- **User:** `vchuser` with home at `/home/vchuser` + +## Preview Access (IMPORTANT) + +Apps are accessed through a **reverse proxy**, not directly. There are two modes: + +1. **Subdomain proxy** (preferred): `https://lxc{VMID}-{PORT}.dev.example.com/` — your app is at the domain root, everything works normally. +2. **Path-based proxy** (fallback): `https://host/proxy/{INSTANCE}/p/{PORT}/` — your app is behind a path prefix. + +### Rules for portable apps that work in both modes: + +- **ALWAYS use relative paths** for assets, API calls, and links: + - `css/style.css` ✅ — NOT `/css/style.css` ❌ + - `api/users` ✅ — NOT `/api/users` ❌ + - `fetch('api/data')` ✅ — NOT `fetch('/api/data')` ❌ +- **Static file serving:** Configure your server to serve from the request path, not the filesystem root. +- **`` tag:** If you must use absolute paths, add `` in ``. + +### Framework-specific configuration: + +- **Express:** `app.use(express.static('public'))` works — just ensure HTML references are relative. +- **Vite:** Set `base: './'` in `vite.config.ts`. +- **Next.js:** Set `basePath` in `next.config.js` if using path-based proxy, or leave default for subdomain proxy. +- **Create React App:** Set `"homepage": "."` in `package.json`. + +## Development Conventions + +- Write clear, descriptive commit messages (imperative mood: "Add feature", not "Added feature") +- Prefer small, focused commits over large ones +- Run linters and formatters before committing +- Write tests for critical business logic +- Keep dependencies minimal — use native browser/Node APIs where possible + +## README Requirement (MANDATORY) + +Every project MUST have a meaningful `README.md` in the project root. This is enforced by the VCH audit system — projects without a proper README cannot be published. + +Your README must include at minimum: +- **Project description** — what the project does (not just the template placeholder) +- **Installation instructions** — how to install dependencies +- **Development instructions** — how to start the dev server +- **Tech stack** — what technologies are used + +Update the README whenever you add features, change setup steps, or modify the tech stack. The audit will reject READMEs that are still just the default template. + +## Project Description (MANDATORY) + +Every project has a `.vch-description` file in the project root. Keep this file up to date with a clear, non-technical description of your project: +- What the project does +- What problem it solves or what it's used for +- Who it's for + +Do NOT include technical details (tech stack, dependencies, setup instructions) — those belong in the README. The `.vch-description` content is shown in the VCH Showcase and is synced automatically when an audit runs. + +Update `.vch-description` whenever the project's purpose or scope changes significantly. + +## Web Best Practices + +- Use semantic HTML elements (`nav`, `main`, `article`, `section`, etc.) +- Mobile-first responsive design +- Follow accessibility guidelines (ARIA labels, keyboard navigation, color contrast) +- Optimize images and assets for performance +- Use environment variables for configuration — never hardcode secrets + +## Commands + +Fill in project-specific commands below: + +- **Install dependencies:** `npm install` +- **Start dev server:** `npm run dev` +- **Run tests:** `npm test` +- **Build for production:** `npm run build` +- **Lint:** `npm run lint` diff --git a/README.md b/README.md new file mode 100644 index 0000000..30883d6 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Reelkit + +Desktop UI for assembling weekly "Best-of" video reels from WhatsApp clips. Shows the Clip-Auswahl (clip selection) screen: browse transcribed clips, review AI recommendations, pick what goes into the reel, and reorder via drag-and-drop. + +## Installation + +```bash +npm install +``` + +## Development + +```bash +npm run dev +``` + +## Build for production + +```bash +npm run build +``` + +## Tech Stack + +- React 18 +- Vite 6 +- Plain CSS with CSS custom properties (oklch color space) +- Inter Tight + JetBrains Mono (Google Fonts) + +## Project Structure + +``` +src/ + main.jsx — React entry point + App.jsx — Main app: clip list, sidebar, sticky footer + TweaksPanel.jsx — Floating tweaks panel (theme, density, export settings, …) + styles.css — All styles via CSS custom properties +``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..db64972 --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + Reelkit — Clip-Auswahl · KW 17 + + + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..467395b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1794 @@ +{ + "name": "reelkit", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reelkit", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.3.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.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.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "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.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "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.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@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.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "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.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "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/baseline-browser-mapping": { + "version": "2.10.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", + "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001790", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", + "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==", + "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/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/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/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "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/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/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/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/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/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/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/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/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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "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": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/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/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "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.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "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/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..e559fad --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "reelkit", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint src" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.3.3" + } +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..c5bd8c6 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,569 @@ +import { useState, useEffect, useMemo, useRef } from 'react' +import './styles.css' +import { + useTweaks, TweaksPanel, TweakSection, + TweakSlider, TweakToggle, TweakRadio, TweakSelect, +} from './TweaksPanel.jsx' + +// ===== DATA ===== +const CLIPS_SEED = [ + { id: "c1", name: "IMG-20260418-WA0023.mp4", from: "Mama", duration: 18, size: "4.2 MB", hue: 18, status: "done", rec: "empfohlen", reason: "Klare Pointe am Ende, gut hörbar, reagiert direkt auf Papas Witz.", transcript: "… also ich sag' euch, der Hund hat sich den ganzen Kuchen geschnappt und ist damit unter den Tisch —", tags: ["Familie","Moment"] }, + { id: "c2", name: "IMG-20260418-WA0027.mp4", from: "Jonas", duration: 42, size: "9.8 MB", hue: 210, status: "done", rec: "empfohlen", reason: "Starker visueller Moment (Sonnenuntergang), ruhig eingesprochen.", transcript: "Guck mal wie das Licht gerade auf dem Wasser tanzt, das ist so verrückt, ich steh' hier schon seit zehn Minuten.", tags: ["Outdoor","Ruhig"] }, + { id: "c3", name: "IMG-20260418-WA0031.mp4", from: "Lena", duration: 8, size: "1.9 MB", hue: 340, status: "done", rec: "optional", reason: "Kurz und charmant, aber Ton etwas leise.", transcript: "Heey, ich wollt nur kurz sagen — ich hab's geschafft!", tags: ["Kurz"] }, + { id: "c4", name: "IMG-20260419-WA0002.mp4", from: "Papa", duration: 55, size: "12.1 MB", hue: 35, status: "done", rec: "überspringen", reason: "Lange Stille in der Mitte, Kameraruckler ab 0:32.", transcript: "So, jetzt müsste das eigentlich funktionieren … ähm … moment …", tags: ["Technik"] }, + { id: "c5", name: "IMG-20260419-WA0014.mp4", from: "Tante Rita", duration: 23, size: "5.4 MB", hue: 120, status: "done", rec: "empfohlen", reason: "Warme Begrüßung, gute Energie, passender Einstieg.", transcript: "Ihr Lieben, ich wünsch euch allen einen wunderbaren Sonntag und denkt dran —", tags: ["Gruß","Warm"] }, + { id: "c6", name: "IMG-20260419-WA0019.mp4", from: "Max", duration: 31, size: "7.2 MB", hue: 260, status: "done", rec: "optional", reason: "Gute Musik im Hintergrund, Sprache teils überlagert.", transcript: "Das Konzert gestern war einfach — ich hab keine Worte, Leute, das war —", tags: ["Musik"] }, + { id: "c7", name: "IMG-20260420-WA0005.mp4", from: "Mama", duration: 12, size: "2.8 MB", hue: 18, status: "done", rec: "empfohlen", reason: "Lacher, hohe emotionale Dichte in kurzer Zeit.", transcript: "Nein nein nein das kann nicht sein haha das ist nicht euer Ernst —", tags: ["Lacher"] }, + { id: "c8", name: "IMG-20260420-WA0011.mp4", from: "Jonas", duration: 47, size: "10.9 MB", hue: 210, status: "transcribing", progress: 64, rec: null, reason: null, transcript: null, tags: [] }, + { id: "c9", name: "IMG-20260420-WA0018.mp4", from: "Lena", duration: 15, size: "3.4 MB", hue: 340, status: "transcribing", progress: 22, rec: null, reason: null, transcript: null, tags: [] }, + { id: "c10", name: "IMG-20260421-WA0003.mp4", from: "Papa", duration: 29, size: "6.7 MB", hue: 35, status: "queued", rec: null, reason: null, transcript: null, tags: [] }, + { id: "c11", name: "IMG-20260421-WA0009.mp4", from: "Oma", duration: 38, size: "8.6 MB", hue: 80, status: "done", rec: "empfohlen", reason: "Seltener O-Ton von Oma, sehr bewegend.", transcript: "Weißt du, als ich in deinem Alter war, gab's das alles noch nicht und trotzdem —", tags: ["Herz","Familie"] }, + { id: "c12", name: "IMG-20260421-WA0021.mp4", from: "Max", duration: 19, size: "4.5 MB", hue: 260, status: "done", rec: "optional", reason: "Solider Moment, aber thematisch ähnlich zu Clip 06.", transcript: "Und dann hat er noch gesagt, dass das nächste Album schon —", tags: ["Musik"] }, + { id: "c13", name: "IMG-20260422-WA0001.mp4", from: "Tante Rita", duration: 52, size: "11.8 MB", hue: 120, status: "done", rec: "überspringen", reason: "Großer Teil ist Anleitung, wenig Reel-Material.", transcript: "Also ihr nehmt zuerst die Zwiebeln, schneidet die klein, dann kommt der Knoblauch dazu —", tags: ["Tutorial"] }, + { id: "c14", name: "IMG-20260422-WA0008.mp4", from: "Jonas", duration: 11, size: "2.6 MB", hue: 210, status: "done", rec: "empfohlen", reason: "Punchline mit klarer Pointe, starker Abschluss.", transcript: "… und das, meine Damen und Herren, ist der Grund warum ich keine Katzen mag.", tags: ["Punchline"] }, + { id: "c15", name: "IMG-20260422-WA0015.mp4", from: "Mama", duration: 26, size: "6.0 MB", hue: 18, status: "done", rec: "optional", reason: "Nett, aber wiederholt Thema aus Clip 01.", transcript: "Der Hund macht's schon wieder, der sitzt schon wieder unter'm Tisch —", tags: ["Familie"] }, + { id: "c16", name: "IMG-20260423-WA0004.mp4", from: "Lena", duration: 33, size: "7.5 MB", hue: 340, status: "done", rec: "empfohlen", reason: "Persönliche Reflexion, gut geschnitten, ruhige Kamera.", transcript: "Ich hab in der Woche echt viel über das nachgedacht was du letztens meintest und —", tags: ["Persönlich"] }, + { id: "c17", name: "IMG-20260423-WA0012.mp4", from: "Max", duration: 44, size: "10.2 MB", hue: 260, status: "done", rec: "überspringen", reason: "Vertikal/horizontal-Wechsel, Schnitt wird unruhig.", transcript: "Wartet, ich dreh mal um — nee andersrum — ok jetzt —", tags: ["Unruhig"] }, + { id: "c18", name: "IMG-20260423-WA0020.mp4", from: "Papa", duration: 21, size: "4.9 MB", hue: 35, status: "done", rec: "optional", reason: "Ordentlicher Ton, Inhalt wiederholt sich später.", transcript: "Hab gestern das neue Regal aufgebaut und es steht, gradmal vier Stunden gedauert.", tags: ["Alltag"] }, +] + +const SELECTED_SEED = ["c2","c5","c1","c7","c14","c16","c11"] + +// ===== THUMB ===== +function Thumb({ hue, duration, selected, index, w = 180, h = 108 }) { + const bg = `linear-gradient(135deg, oklch(0.22 0.04 ${hue}) 0%, oklch(0.14 0.03 ${hue}) 60%, oklch(0.09 0.02 ${hue}) 100%)` + const mm = String(Math.floor(duration / 60)).padStart(1, "0") + const ss = String(duration % 60).padStart(2, "0") + return ( +
+ + + + + + + + +
+ +
+
{mm}:{ss}
+ {selected &&
#{index}
} +
+ ) +} + +// ===== SIDEBAR ===== +function Sidebar({ current, counts }) { + const steps = [ + { id: "import", label: "Import", sub: `${counts.total} Clips geladen` }, + { id: "transcribe",label: "Transkription", sub: `${counts.done}/${counts.total} fertig` }, + { id: "select", label: "Clip-Auswahl", sub: `${counts.selected} ausgewählt` }, + { id: "export", label: "Assembly & Export", sub: `~${counts.totalDur}s Reel` }, + ] + return ( + + ) +} + +// ===== CLIP ROW ===== +function ClipRow({ clip, selected, orderIndex, onToggle, onDrag, onDragOver, onDrop, onHover, hovered, tweaks }) { + const thumbSizes = { small: { w: 130, h: 78 }, medium: { w: 180, h: 108 }, large: { w: 230, h: 138 } } + const ts = thumbSizes[tweaks.thumbSize] || thumbSizes.medium + const recMeta = { + "empfohlen": { dot: "rec-good", label: "empfohlen" }, + "optional": { dot: "rec-mid", label: "optional" }, + "überspringen": { dot: "rec-skip", label: "überspringen" }, + }[clip.rec] || null + + const statusLabel = { + "queued": "in Warteschlange", + "transcribing": `transkribiere … ${clip.progress ?? 0}%`, + "done": "Transkript fertig", + }[clip.status] + + return ( +
onDrag(e, clip.id)} + onDragOver={(e) => onDragOver(e, clip.id)} + onDrop={(e) => onDrop(e, clip.id)} + onMouseEnter={() => onHover(clip.id)} + onMouseLeave={() => onHover(null)} + > +
+ + {selected &&
#{orderIndex}
} +
+ +
+ +
+ +
+
+
+ {clip.from} + {tweaks.showFilename && <> + · + {clip.name} + } +
+ {tweaks.showMetaBar &&
+ {clip.duration}s + · + {clip.size} + · + + + {statusLabel} + +
} +
+ + {clip.status === "done" ? ( +
+ + {clip.transcript} + " +
+ ) : clip.status === "transcribing" ? ( +
+
+
Whisper · medium · verarbeite Audio …
+
+ ) : ( +
+
wartet auf GPU-Slot
+
+ )} + + {tweaks.showTags && clip.tags.length > 0 && ( +
+ {clip.tags.map(t => {t})} +
+ )} +
+ +
+ {recMeta && ( +
+
+ + {recMeta.label} +
+ {tweaks.showReasons && clip.reason &&
{clip.reason}
} +
+ )} +
+ +
+ + + + + +
+
+ ) +} + +// ===== MAIN ===== +export default function App() { + const [clips, setClips] = useState(CLIPS_SEED) + const [selected, setSelected] = useState(SELECTED_SEED) + const [filter, setFilter] = useState("alle") + const [sort, setSort] = useState("empfehlung") + const [query, setQuery] = useState("") + const [hovered, setHovered] = useState(null) + const dragId = useRef(null) + + const DEFAULTS = { + density: "comfortable", + showReasons: true, + accent: "moss", + targetLength: 90, + theme: "light", + thumbSize: "medium", + transcriptLines: 2, + showTags: true, + showFilename: true, + showMetaBar: true, + autoSelectRecommended: false, + groupBySender: false, + hideSkipped: false, + whisperModel: "medium", + whisperLang: "de", + aiStrictness: 2, + exportFormat: "mp4", + exportResolution: "1080p", + aspectRatio: "9:16", + addCrossfade: true, + crossfadeMs: 300, + addCaptions: false, + captionStyle: "minimal", + musicBed: "keine", + musicVolume: 20, + watermark: false, + minClipLength: 4, + maxClipLength: 60, + fontFamily: "inter-tight", + radius: 10, + cornerStyle: "soft", + } + const [tweaks, setTweaks] = useTweaks(DEFAULTS) + + // Simulate transcription progress + useEffect(() => { + const t = setInterval(() => { + setClips(cs => cs.map(c => { + if (c.status === "transcribing") { + const p = Math.min(100, (c.progress ?? 0) + 3) + if (p >= 100) return { ...c, status: "done", progress: 100, rec: "optional", reason: "Frisch transkribiert, noch ungeprüft.", transcript: "… (gerade fertig transkribiert) …", tags: ["Neu"] } + return { ...c, progress: p } + } + return c + })) + }, 1200) + return () => clearInterval(t) + }, []) + + // Apply tweaks to CSS vars + useEffect(() => { + const map = { + amber: { h: 60, c: 0.13 }, + ocean: { h: 230, c: 0.10 }, + moss: { h: 145, c: 0.09 }, + plum: { h: 340, c: 0.10 }, + crimson: { h: 25, c: 0.14 }, + graphite:{ h: 260, c: 0.02 }, + } + const a = map[tweaks.accent] || map.moss + const root = document.documentElement + root.style.setProperty("--accent", `oklch(0.68 ${a.c} ${a.h})`) + root.style.setProperty("--accent-2", `oklch(0.58 ${a.c} ${a.h})`) + root.style.setProperty("--accent-bg", `oklch(0.96 ${a.c * 0.3} ${a.h})`) + root.style.setProperty("--density", tweaks.density === "compact" ? "10px" : tweaks.density === "spacious" ? "22px" : "16px") + root.style.setProperty("--radius", tweaks.cornerStyle === "sharp" ? "2px" : tweaks.cornerStyle === "round" ? "16px" : "10px") + const fonts = { + "inter-tight": '"Inter Tight", "Inter", system-ui, sans-serif', + "geist": '"Geist", "Inter", system-ui, sans-serif', + "ibm-plex": '"IBM Plex Sans", system-ui, sans-serif', + "system": 'system-ui, -apple-system, sans-serif', + } + root.style.setProperty("--font", fonts[tweaks.fontFamily] || fonts["inter-tight"]) + root.style.fontFamily = fonts[tweaks.fontFamily] || fonts["inter-tight"] + if (tweaks.theme === "dark") { + root.style.setProperty("--bg", "oklch(0.18 0.01 80)") + root.style.setProperty("--bg-2", "oklch(0.22 0.01 80)") + root.style.setProperty("--panel", "oklch(0.24 0.01 80)") + root.style.setProperty("--line", "oklch(0.32 0.01 80)") + root.style.setProperty("--line-2", "oklch(0.38 0.01 80)") + root.style.setProperty("--ink", "oklch(0.96 0.004 80)") + root.style.setProperty("--ink-2", "oklch(0.78 0.008 80)") + root.style.setProperty("--ink-3", "oklch(0.58 0.006 80)") + } else { + root.style.setProperty("--bg", "oklch(0.985 0.004 85)") + root.style.setProperty("--bg-2", "oklch(0.97 0.004 85)") + root.style.setProperty("--panel", "#ffffff") + root.style.setProperty("--line", "oklch(0.92 0.004 85)") + root.style.setProperty("--line-2", "oklch(0.88 0.004 85)") + root.style.setProperty("--ink", "oklch(0.18 0.01 80)") + root.style.setProperty("--ink-2", "oklch(0.38 0.008 80)") + root.style.setProperty("--ink-3", "oklch(0.58 0.006 80)") + } + const thumbCols = { small: "130px", medium: "180px", large: "230px" } + root.style.setProperty("--thumb-col", thumbCols[tweaks.thumbSize] || thumbCols.medium) + }, [tweaks.accent, tweaks.density, tweaks.cornerStyle, tweaks.fontFamily, tweaks.theme, tweaks.thumbSize]) + + const filtered = useMemo(() => { + let list = clips.slice() + if (filter === "empfohlen") list = list.filter(c => c.rec === "empfohlen") + if (filter === "pending") list = list.filter(c => c.status !== "done") + if (filter === "ausgewählt") list = list.filter(c => selected.includes(c.id)) + if (tweaks.hideSkipped) list = list.filter(c => c.rec !== "überspringen") + if (query.trim()) { + const q = query.toLowerCase() + list = list.filter(c => (c.transcript || "").toLowerCase().includes(q) || c.from.toLowerCase().includes(q) || c.name.toLowerCase().includes(q)) + } + if (sort === "empfehlung") { + const rank = { "empfohlen": 0, "optional": 1, "überspringen": 2 } + list.sort((a, b) => (rank[a.rec] ?? 3) - (rank[b.rec] ?? 3)) + } else if (sort === "dauer") { + list.sort((a, b) => b.duration - a.duration) + } else if (sort === "absender") { + list.sort((a, b) => a.from.localeCompare(b.from)) + } + return list + }, [clips, filter, sort, query, selected, tweaks.hideSkipped]) + + // Auto-select recommended + useEffect(() => { + if (!tweaks.autoSelectRecommended) return + setSelected(s => { + const add = clips.filter(c => c.rec === "empfohlen" && !s.includes(c.id)).map(c => c.id) + return add.length ? [...s, ...add] : s + }) + }, [tweaks.autoSelectRecommended, clips]) + + const toggle = (id) => { + setSelected(s => s.includes(id) ? s.filter(x => x !== id) : [...s, id]) + } + + const onDrag = (e, id) => { dragId.current = id; e.dataTransfer.effectAllowed = "move" } + const onDragOver = (e, id) => { + if (!dragId.current || dragId.current === id) return + e.preventDefault() + } + const onDrop = (e, id) => { + if (!dragId.current || dragId.current === id) return + e.preventDefault() + setSelected(s => { + if (!s.includes(dragId.current) || !s.includes(id)) return s + const next = s.filter(x => x !== dragId.current) + const idx = next.indexOf(id) + next.splice(idx, 0, dragId.current) + return next + }) + dragId.current = null + } + + const counts = { + total: clips.length, + done: clips.filter(c => c.status === "done").length, + selected: selected.length, + totalDur: selected.reduce((n, id) => n + (clips.find(c => c.id === id)?.duration || 0), 0), + } + const overTarget = counts.totalDur > tweaks.targetLength + + return ( +
+ + +
+
+
+
Projekt · KW 17 · Clip-Auswahl
+

Welche Clips kommen ins Reel?

+

18 Clips, 7 bereits empfohlen. Wähle aus, sortiere per Drag-and-drop und prüfe die KI-Begründung. Transkripte laufen im Hintergrund.

+
+
+ + +
+
+ +
+
+
Auswahl
+
+ {counts.selected} + Clips +
+
+
+
+
Länge gewählt
+
+ {counts.totalDur}s + / Ziel {tweaks.targetLength}s +
+
+
+
+
+
+
+
Transkription
+
+ {counts.done} + / {counts.total} fertig +
+
+
+
+
+
+
+
KI-Empfehlungen
+
+ 7 empfohlen + 5 optional + 3 überspringen +
+
+
+ +
+
+ {[ + ["alle", `Alle · ${clips.length}`], + ["empfohlen", `Nur empfohlen · ${clips.filter(c => c.rec === "empfohlen").length}`], + ["ausgewählt", `Ausgewählt · ${selected.length}`], + ["pending", `In Arbeit · ${clips.filter(c => c.status !== "done").length}`], + ].map(([k, l]) => ( + + ))} +
+
+
+ + setQuery(e.target.value)} /> +
+ +
+
+ +
+
+
+
Vorschau
+
Absender & Transkript
+
KI-Empfehlung
+
+
+ {filtered.map(c => { + const orderIndex = selected.indexOf(c.id) + 1 + return ( + + ) + })} +
+ +
+
+
Reihenfolge deines Reels
+
+ {selected.length === 0 && Noch keine Clips ausgewählt.} + {selected.map((id, i) => { + const c = clips.find(x => x.id === id) + if (!c) return null + return ( + +
+
+
+
#{i + 1} {c.from}
+
{c.duration}s
+
+
+ {i < selected.length - 1 &&
} + + ) + })} +
+
+
+ + +
+
+
+ + + + setTweaks('theme', v)} /> + setTweaks('accent', v)} /> + setTweaks('fontFamily', v)} /> + setTweaks('cornerStyle', v)} /> + + + + setTweaks('density', v)} /> + setTweaks('thumbSize', v)} /> + setTweaks('transcriptLines', v)} /> + setTweaks('showReasons', v)} /> + setTweaks('showTags', v)} /> + setTweaks('showFilename', v)} /> + setTweaks('showMetaBar', v)} /> + + + + setTweaks('autoSelectRecommended', v)} /> + setTweaks('groupBySender', v)} /> + setTweaks('hideSkipped', v)} /> + + + + setTweaks('whisperModel', v)} /> + setTweaks('whisperLang', v)} /> + setTweaks('aiStrictness', v)} /> + setTweaks('minClipLength', v)} /> + setTweaks('maxClipLength', v)} /> + + + + setTweaks('targetLength', v)} /> + setTweaks('aspectRatio', v)} /> + setTweaks('exportResolution', v)} /> + setTweaks('exportFormat', v)} /> + setTweaks('addCrossfade', v)} /> + {tweaks.addCrossfade && setTweaks('crossfadeMs', v)} />} + setTweaks('addCaptions', v)} /> + {tweaks.addCaptions && setTweaks('captionStyle', v)} />} + setTweaks('musicBed', v)} /> + {tweaks.musicBed !== "keine" && setTweaks('musicVolume', v)} />} + setTweaks('watermark', v)} /> + + +
+ ) +} diff --git a/src/TweaksPanel.jsx b/src/TweaksPanel.jsx new file mode 100644 index 0000000..0d50aad --- /dev/null +++ b/src/TweaksPanel.jsx @@ -0,0 +1,358 @@ +import { useState, useEffect, useRef, useCallback } from 'react' + +const __TWEAKS_STYLE = ` + .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px; + max-height:calc(100vh - 32px);display:flex;flex-direction:column; + background:rgba(250,249,247,.78);color:#29261b; + -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%); + border:.5px solid rgba(255,255,255,.6);border-radius:14px; + box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18); + font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden} + .twk-hd{display:flex;align-items:center;justify-content:space-between; + padding:10px 8px 10px 14px;cursor:move;user-select:none} + .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em} + .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55); + width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1} + .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b} + .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px; + overflow-y:auto;overflow-x:hidden;min-height:0; + scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent} + .twk-body::-webkit-scrollbar{width:8px} + .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px} + .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px; + border:2px solid transparent;background-clip:content-box} + .twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25); + border:2px solid transparent;background-clip:content-box} + .twk-row{display:flex;flex-direction:column;gap:5px} + .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px} + .twk-lbl{display:flex;justify-content:space-between;align-items:baseline; + color:rgba(41,38,27,.72)} + .twk-lbl>span:first-child{font-weight:500} + .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums} + + .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase; + color:rgba(41,38,27,.45);padding:10px 0 0} + .twk-sect:first-child{padding-top:0} + + .twk-field{appearance:none;width:100%;height:26px;padding:0 8px; + border:.5px solid rgba(0,0,0,.1);border-radius:7px; + background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none} + .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)} + select.twk-field{padding-right:22px; + background-image:url("data:image/svg+xml;utf8,"); + background-repeat:no-repeat;background-position:right 8px center} + + .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0; + border-radius:999px;background:rgba(0,0,0,.12);outline:none} + .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none; + width:14px;height:14px;border-radius:50%;background:#fff; + border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default} + .twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%; + background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default} + + .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px; + background:rgba(0,0,0,.06);user-select:none} + .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px; + background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12); + transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s} + .twk-seg.dragging .twk-seg-thumb{transition:none} + .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0; + background:transparent;color:inherit;font:inherit;font-weight:500;height:22px; + border-radius:6px;cursor:default;padding:0} + + .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px; + background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0} + .twk-toggle[data-on="1"]{background:#34c759} + .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%; + background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s} + .twk-toggle[data-on="1"] i{transform:translateX(14px)} + + .twk-num{display:flex;align-items:center;height:26px;padding:0 0 0 8px; + border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)} + .twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize; + user-select:none;padding-right:8px} + .twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent; + font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0; + outline:none;color:inherit;-moz-appearance:textfield} + .twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{ + -webkit-appearance:none;margin:0} + .twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)} + + .twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px; + background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default} + .twk-btn:hover{background:rgba(0,0,0,.88)} + .twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit} + .twk-btn.secondary:hover{background:rgba(0,0,0,.1)} + + .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px; + border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default; + background:transparent;flex-shrink:0} + .twk-swatch::-webkit-color-swatch-wrapper{padding:0} + .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px} + .twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px} +` + +export function useTweaks(defaults) { + const init = { ...defaults, ...(window.__INITIAL_TWEAKS__ || {}) } + const [values, setValues] = useState(init) + const setTweak = useCallback((key, val) => { + setValues((prev) => ({ ...prev, [key]: val })) + window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [key]: val } }, '*') + }, []) + return [values, setTweak] +} + +export function TweaksPanel({ title = 'Tweaks', children }) { + const [open, setOpen] = useState(false) + const dragRef = useRef(null) + const offsetRef = useRef({ x: 16, y: 16 }) + const PAD = 16 + + const clampToViewport = useCallback(() => { + const panel = dragRef.current + if (!panel) return + const w = panel.offsetWidth, h = panel.offsetHeight + const maxRight = Math.max(PAD, window.innerWidth - w - PAD) + const maxBottom = Math.max(PAD, window.innerHeight - h - PAD) + offsetRef.current = { + x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)), + y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)), + } + panel.style.right = offsetRef.current.x + 'px' + panel.style.bottom = offsetRef.current.y + 'px' + }, []) + + useEffect(() => { + if (!open) return + clampToViewport() + if (typeof ResizeObserver === 'undefined') { + window.addEventListener('resize', clampToViewport) + return () => window.removeEventListener('resize', clampToViewport) + } + const ro = new ResizeObserver(clampToViewport) + ro.observe(document.documentElement) + return () => ro.disconnect() + }, [open, clampToViewport]) + + useEffect(() => { + const onMsg = (e) => { + const t = e?.data?.type + if (t === '__activate_edit_mode') setOpen(true) + else if (t === '__deactivate_edit_mode') setOpen(false) + } + window.addEventListener('message', onMsg) + window.parent.postMessage({ type: '__edit_mode_available' }, '*') + return () => window.removeEventListener('message', onMsg) + }, []) + + const dismiss = () => { + setOpen(false) + window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*') + } + + const onDragStart = (e) => { + const panel = dragRef.current + if (!panel) return + const r = panel.getBoundingClientRect() + const sx = e.clientX, sy = e.clientY + const startRight = window.innerWidth - r.right + const startBottom = window.innerHeight - r.bottom + const move = (ev) => { + offsetRef.current = { + x: startRight - (ev.clientX - sx), + y: startBottom - (ev.clientY - sy), + } + clampToViewport() + } + const up = () => { + window.removeEventListener('mousemove', move) + window.removeEventListener('mouseup', up) + } + window.addEventListener('mousemove', move) + window.addEventListener('mouseup', up) + } + + if (!open) return null + return ( + <> + +
+
+ {title} + +
+
{children}
+
+ + ) +} + +export function TweakSection({ label, children }) { + return ( + <> +
{label}
+ {children} + + ) +} + +export function TweakRow({ label, value, children, inline = false }) { + return ( +
+
+ {label} + {value != null && {value}} +
+ {children} +
+ ) +} + +export function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) { + return ( + + onChange(Number(e.target.value))} /> + + ) +} + +export function TweakToggle({ label, value, onChange }) { + return ( +
+
{label}
+ +
+ ) +} + +export function TweakRadio({ label, value, options, onChange }) { + const trackRef = useRef(null) + const [dragging, setDragging] = useState(false) + const opts = options.map((o) => (typeof o === 'object' ? o : { value: o, label: o })) + const idx = Math.max(0, opts.findIndex((o) => o.value === value)) + const n = opts.length + + const valueRef = useRef(value) + valueRef.current = value + + const segAt = (clientX) => { + const r = trackRef.current.getBoundingClientRect() + const inner = r.width - 4 + const i = Math.floor(((clientX - r.left - 2) / inner) * n) + return opts[Math.max(0, Math.min(n - 1, i))].value + } + + const onPointerDown = (e) => { + setDragging(true) + const v0 = segAt(e.clientX) + if (v0 !== valueRef.current) onChange(v0) + const move = (ev) => { + if (!trackRef.current) return + const v = segAt(ev.clientX) + if (v !== valueRef.current) onChange(v) + } + const up = () => { + setDragging(false) + window.removeEventListener('pointermove', move) + window.removeEventListener('pointerup', up) + } + window.addEventListener('pointermove', move) + window.addEventListener('pointerup', up) + } + + return ( + +
+
+ {opts.map((o) => ( + + ))} +
+ + ) +} + +export function TweakSelect({ label, value, options, onChange }) { + return ( + + + + ) +} + +export function TweakText({ label, value, placeholder, onChange }) { + return ( + + onChange(e.target.value)} /> + + ) +} + +export function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) { + const clamp = (n) => { + if (min != null && n < min) return min + if (max != null && n > max) return max + return n + } + const startRef = useRef({ x: 0, val: 0 }) + const onScrubStart = (e) => { + e.preventDefault() + startRef.current = { x: e.clientX, val: value } + const decimals = (String(step).split('.')[1] || '').length + const move = (ev) => { + const dx = ev.clientX - startRef.current.x + const raw = startRef.current.val + dx * step + const snapped = Math.round(raw / step) * step + onChange(clamp(Number(snapped.toFixed(decimals)))) + } + const up = () => { + window.removeEventListener('pointermove', move) + window.removeEventListener('pointerup', up) + } + window.addEventListener('pointermove', move) + window.addEventListener('pointerup', up) + } + return ( +
+ {label} + onChange(clamp(Number(e.target.value)))} /> + {unit && {unit}} +
+ ) +} + +export function TweakColor({ label, value, onChange }) { + return ( +
+
{label}
+ onChange(e.target.value)} /> +
+ ) +} + +export function TweakButton({ label, onClick, secondary = false }) { + return ( + + ) +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..3d9da8a --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,9 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..cbe78e9 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,325 @@ +:root { + --bg: oklch(0.985 0.004 85); + --bg-2: oklch(0.97 0.004 85); + --panel: #ffffff; + --line: oklch(0.92 0.004 85); + --line-2: oklch(0.88 0.004 85); + --ink: oklch(0.18 0.01 80); + --ink-2: oklch(0.38 0.008 80); + --ink-3: oklch(0.58 0.006 80); + --accent: oklch(0.68 0.09 145); + --accent-2: oklch(0.58 0.09 145); + --accent-bg: oklch(0.96 0.027 145); + --rec-good: oklch(0.62 0.11 150); + --rec-mid: oklch(0.70 0.09 85); + --rec-skip: oklch(0.60 0.04 30); + --density: 16px; + --radius: 10px; + --radius-sm: 6px; + --shadow: 0 1px 2px rgba(20,15,10,0.04), 0 4px 20px -8px rgba(20,15,10,0.06); + --font: "Inter Tight", "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif; + font-family: var(--font); +} + +* { box-sizing: border-box; } + +html, body { margin: 0; padding: 0; background: var(--bg); color: var(--ink); font-size: 14px; line-height: 1.45; } +.mono { font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-feature-settings: "tnum"; } + +button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } +input, select { font-family: inherit; font-size: inherit; color: inherit; } + +.app { display: grid; grid-template-columns: 280px 1fr; min-height: 100vh; } + +/* ===== SIDEBAR ===== */ +.sidebar { + background: var(--bg-2); + border-right: 1px solid var(--line); + padding: 28px 22px; + display: flex; + flex-direction: column; + gap: 28px; + position: sticky; + top: 0; + height: 100vh; +} +.brand { display: flex; align-items: center; gap: 12px; } +.brand-mark { display: grid; grid-template-columns: repeat(3, 4px); gap: 3px; width: 28px; height: 28px; align-content: center; justify-content: center; background: var(--ink); border-radius: 6px; padding: 6px; } +.brand-mark > div { background: var(--bg-2); height: 4px; border-radius: 1px; } +.brand-mark > div:nth-child(2) { background: var(--accent); } +.brand-name { font-weight: 600; font-size: 15px; letter-spacing: -0.01em; } +.brand-sub { font-size: 11px; color: var(--ink-3); letter-spacing: 0.04em; text-transform: uppercase; } + +.project { + border: 1px solid var(--line); + border-radius: var(--radius); + padding: 14px; + background: var(--panel); +} +.project-label { font-size: 10px; color: var(--ink-3); letter-spacing: 0.08em; text-transform: uppercase; } +.project-name { font-weight: 600; font-size: 14px; margin-top: 4px; letter-spacing: -0.01em; } +.project-meta { font-size: 12px; color: var(--ink-3); margin-top: 4px; } + +.steps { display: flex; flex-direction: column; gap: 2px; } +.step { + display: flex; + gap: 12px; + padding: 10px 12px; + border-radius: 8px; + text-decoration: none; + color: var(--ink-2); + position: relative; + transition: background 0.12s; + cursor: default; +} +.step:hover { background: rgba(0,0,0,0.025); } +.step.is-current { background: var(--panel); box-shadow: 0 0 0 1px var(--line); } +.step.is-current .step-label { color: var(--ink); font-weight: 600; } +.step.is-current .step-num { color: var(--accent-2); background: var(--accent-bg); } +.step.is-done .step-num { background: var(--ink); color: var(--bg); border-color: var(--ink); } +.step-num { + width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--line); + display: grid; place-items: center; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--ink-3); + flex-shrink: 0; +} +.step-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; } +.step-label { font-size: 13px; } +.step-sub { font-size: 11px; color: var(--ink-3); } + +.side-foot { margin-top: auto; font-size: 12px; color: var(--ink-3); display: flex; flex-direction: column; gap: 8px; } +.foot-row { display: flex; justify-content: space-between; } +.foot-bar { height: 3px; background: var(--line); border-radius: 2px; overflow: hidden; } +.foot-bar > div { height: 100%; background: var(--ink-2); } +.foot-hint { font-size: 11px; } + +/* ===== MAIN ===== */ +.main { padding: 36px 44px 140px; max-width: 1400px; } + +.top { display: flex; justify-content: space-between; align-items: flex-end; gap: 32px; margin-bottom: 28px; } +.breadcrumb { font-size: 11px; color: var(--ink-3); letter-spacing: 0.04em; margin-bottom: 8px; } +.breadcrumb span { color: var(--ink); } +.top h1 { margin: 0; font-size: 30px; font-weight: 600; letter-spacing: -0.015em; line-height: 1.2; } +.lede { margin: 12px 0 0; color: var(--ink-2); font-size: 15px; line-height: 1.6; max-width: 60ch; letter-spacing: 0; text-wrap: pretty; } +.top-actions { display: flex; gap: 10px; align-items: center; flex-shrink: 0; } + +.btn-ghost { + padding: 9px 14px; border-radius: 8px; font-size: 13px; color: var(--ink-2); + border: 1px solid var(--line); background: var(--panel); transition: all 0.12s; +} +.btn-ghost:hover { border-color: var(--line-2); color: var(--ink); } +.btn-primary { + padding: 9px 16px; border-radius: 8px; font-size: 13px; font-weight: 500; + background: var(--ink); color: var(--bg); display: inline-flex; align-items: center; gap: 8px; + transition: background 0.12s; +} +.btn-primary:hover { background: oklch(0.10 0.01 80); } +.btn-primary.big { padding: 12px 20px; font-size: 14px; } + +/* ===== STRIP ===== */ +.strip { + display: grid; grid-template-columns: 1fr 1fr 1fr 1.2fr; + gap: 0; + background: var(--panel); border: 1px solid var(--line); + border-radius: var(--radius); padding: 18px 22px; + margin-bottom: 24px; + align-items: center; +} +.strip-sep { width: 1px; background: var(--line); height: 42px; justify-self: center; display: none; } +.strip-group { display: flex; flex-direction: column; gap: 6px; padding: 0 22px; border-left: 1px solid var(--line); } +.strip-group:first-child { border-left: none; padding-left: 0; } +.strip-group.right { align-items: flex-start; } +.strip-label { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); } +.strip-value { display: flex; align-items: baseline; gap: 6px; } +.strip-value .big { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; font-family: "JetBrains Mono", monospace; font-feature-settings: "tnum"; } +.strip-value .big.over { color: oklch(0.55 0.14 30); } +.strip-value .unit { font-size: 12px; color: var(--ink-3); } +.strip-bar { height: 3px; background: var(--line); border-radius: 2px; overflow: hidden; margin-top: 2px; max-width: 160px; } +.strip-bar > div { height: 100%; background: var(--accent); transition: width 0.3s; } +.strip-bar > div.over { background: oklch(0.60 0.14 30); } + +.rec-legend { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ink-2); } +.rec-legend span { display: inline-flex; align-items: center; gap: 7px; } +.rec-legend i { width: 6px; height: 6px; border-radius: 50%; display: inline-block; } +.rec-legend i.rec-good { background: var(--rec-good); } +.rec-legend i.rec-mid { background: var(--rec-mid); } +.rec-legend i.rec-skip { background: var(--rec-skip); } + +/* ===== TOOLBAR ===== */ +.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 16px; flex-wrap: wrap; } +.filters { display: flex; gap: 6px; } +.chip { + padding: 7px 12px; border-radius: 999px; font-size: 12.5px; + color: var(--ink-2); border: 1px solid var(--line); background: var(--panel); + transition: all 0.12s; +} +.chip:hover { border-color: var(--line-2); } +.chip.is-on { background: var(--ink); color: var(--bg); border-color: var(--ink); } + +.tools { display: flex; gap: 10px; align-items: center; } +.search { + display: flex; align-items: center; gap: 8px; + padding: 7px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); + color: var(--ink-3); +} +.search:focus-within { border-color: var(--ink-2); color: var(--ink-2); } +.search input { border: none; outline: none; background: none; width: 260px; font-size: 13px; color: var(--ink); } +.search input::placeholder { color: var(--ink-3); } +.sort { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-3); } +.sort select { + padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); + font-size: 13px; color: var(--ink); +} + +/* ===== LIST ===== */ +.list { + background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); + overflow: hidden; +} +.list-head { + display: grid; + grid-template-columns: 56px var(--thumb-col, 180px) 1fr 210px 24px; + gap: 20px; + padding: 12px 18px; + border-bottom: 1px solid var(--line); + background: var(--bg-2); + font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); +} +.row { + display: grid; + grid-template-columns: 56px var(--thumb-col, 180px) 1fr 210px 24px; + gap: 20px; + padding: var(--density) 18px; + border-bottom: 1px solid var(--line); + align-items: center; + transition: background 0.1s; + cursor: grab; +} +.row:last-child { border-bottom: none; } +.row:hover, .row.is-hovered { background: var(--bg-2); } +.row.is-selected { background: color-mix(in oklch, var(--accent-bg) 40%, var(--panel)); } +.row.is-selected:hover { background: color-mix(in oklch, var(--accent-bg) 60%, var(--panel)); } +.row.is-pending { opacity: 0.75; cursor: default; } + +.row-check { display: flex; flex-direction: column; align-items: center; gap: 6px; } +.check { + width: 20px; height: 20px; border-radius: 5px; + border: 1.5px solid var(--line-2); background: var(--panel); + display: grid; place-items: center; + transition: all 0.12s; + color: var(--bg); +} +.check:hover:not(:disabled) { border-color: var(--ink-2); } +.check:disabled { opacity: 0.35; cursor: not-allowed; } +.check.is-on { background: var(--accent-2); border-color: var(--accent-2); } +.order-pill { + font-size: 10px; padding: 2px 6px; border-radius: 4px; + background: var(--ink); color: var(--bg); + letter-spacing: 0.02em; +} + +/* THUMB */ +.thumb { + width: 180px; height: 108px; border-radius: var(--radius-sm); + position: relative; overflow: hidden; + box-shadow: 0 1px 2px rgba(0,0,0,0.15), inset 0 0 0 1px rgba(255,255,255,0.04); +} +.thumb-stripes { position: absolute; inset: 0; width: 100%; height: 100%; } +.thumb-play { + position: absolute; inset: 0; display: grid; place-items: center; + background: radial-gradient(circle at center, rgba(0,0,0,0.25), transparent 60%); +} +.thumb-dur { + position: absolute; bottom: 6px; right: 6px; + font-family: "JetBrains Mono", monospace; font-size: 10.5px; + color: #fff; background: rgba(0,0,0,0.6); padding: 2px 6px; border-radius: 3px; + letter-spacing: 0.02em; +} +.thumb-badge { + position: absolute; top: 6px; left: 6px; + font-family: "JetBrains Mono", monospace; font-size: 10.5px; + background: var(--accent); color: var(--ink); padding: 2px 6px; border-radius: 3px; + font-weight: 600; +} + +/* BODY */ +.row-body { min-width: 0; display: flex; flex-direction: column; gap: 6px; } +.row-head { display: flex; flex-direction: column; gap: 2px; } +.row-title { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; } +.row-title .from { font-weight: 600; font-size: 14px; letter-spacing: -0.005em; } +.row-title .sep { color: var(--ink-3); } +.row-title .filename { font-size: 11.5px; color: var(--ink-3); } +.row-meta { font-size: 11.5px; color: var(--ink-3); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.dotsep { opacity: 0.5; } +.status { display: inline-flex; align-items: center; gap: 5px; } +.status-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; display: inline-block; } +.status.s-done { color: var(--rec-good); } +.status.s-transcribing { color: var(--accent-2); } +.status.s-queued { color: var(--ink-3); } + +.transcript { + font-size: 13px; color: var(--ink-2); + line-height: 1.5; + max-width: 62ch; + font-style: italic; + position: relative; + padding-left: 10px; + border-left: 2px solid var(--line-2); +} +.transcript .quote { opacity: 0.4; font-style: normal; } +.transcribing { display: flex; flex-direction: column; gap: 6px; max-width: 360px; } +.transcribe-bar { height: 3px; background: var(--line); border-radius: 2px; overflow: hidden; } +.transcribe-bar > div { height: 100%; background: var(--accent); animation: pulse 2s ease-in-out infinite alternate; } +@keyframes pulse { from { opacity: 0.7; } to { opacity: 1; } } +.transcribe-hint { font-size: 11px; color: var(--ink-3); } + +.tags { display: flex; gap: 5px; margin-top: 2px; } +.tag { font-size: 10.5px; padding: 2px 7px; border-radius: 999px; background: var(--bg-2); color: var(--ink-2); border: 1px solid var(--line); } + +/* REC */ +.row-rec { display: flex; flex-direction: column; gap: 6px; } +.rec { padding: 10px 12px; border-radius: 8px; border: 1px solid var(--line); background: var(--bg-2); } +.rec-head { display: flex; align-items: center; gap: 8px; } +.rec-dot { width: 7px; height: 7px; border-radius: 50%; } +.rec-label { font-size: 12px; font-weight: 600; letter-spacing: -0.005em; } +.rec-reason { font-size: 11.5px; color: var(--ink-2); margin-top: 6px; line-height: 1.4; } +.rec.rec-good .rec-dot { background: var(--rec-good); } +.rec.rec-good { background: color-mix(in oklch, var(--rec-good) 8%, var(--panel)); border-color: color-mix(in oklch, var(--rec-good) 25%, var(--line)); } +.rec.rec-good .rec-label { color: oklch(0.45 0.10 150); } +.rec.rec-mid .rec-dot { background: var(--rec-mid); } +.rec.rec-mid .rec-label { color: oklch(0.48 0.09 85); } +.rec.rec-skip .rec-dot { background: var(--rec-skip); } +.rec.rec-skip .rec-label { color: var(--ink-3); } +.rec.rec-skip { opacity: 0.75; } + +.row-grip { color: var(--ink-3); opacity: 0; transition: opacity 0.12s; cursor: grab; } +.row-grip svg { fill: currentColor; } +.row:hover .row-grip { opacity: 0.5; } + +/* ===== STICKY FOOT ===== */ +.sticky-foot { + position: fixed; bottom: 0; left: 280px; right: 0; + background: color-mix(in oklch, var(--panel) 90%, transparent); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + border-top: 1px solid var(--line); + padding: 16px 44px; + display: flex; justify-content: space-between; align-items: center; gap: 24px; + z-index: 10; +} +.foot-info { display: flex; flex-direction: column; gap: 8px; flex: 1; min-width: 0; } +.foot-title { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); } +.foot-chain { display: flex; gap: 10px; align-items: center; overflow-x: auto; padding-bottom: 2px; } +.foot-chain::-webkit-scrollbar { height: 4px; } +.foot-chain::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 2px; } +.foot-empty { font-size: 12.5px; color: var(--ink-3); } +.chain-clip { + display: flex; align-items: center; gap: 8px; padding: 4px 10px 4px 4px; + background: var(--bg-2); border: 1px solid var(--line); border-radius: 6px; + flex-shrink: 0; +} +.chain-thumb { width: 28px; height: 20px; border-radius: 3px; flex-shrink: 0; } +.chain-meta { display: flex; flex-direction: column; gap: 0; } +.chain-from { font-size: 11.5px; font-weight: 500; letter-spacing: -0.005em; } +.chain-dur { font-size: 10px; color: var(--ink-3); } +.chain-arrow { color: var(--ink-3); font-size: 12px; flex-shrink: 0; } +.foot-actions { display: flex; gap: 10px; flex-shrink: 0; } diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..cecf58e --- /dev/null +++ b/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + base: './', +})