Refactor project structure and update dependencies
- Updated .gitignore to include new build and environment files. - Upgraded dependencies in package.json and bun.lock, including major updates for canvaskit-wasm and tailwind-merge. - Removed outdated package-lock.json and tsconfig files. - Replaced React with Solid.js for improved performance and reactivity. - Introduced new components and updated the app structure for better organization. - Added new SVG icons and fonts for enhanced UI. - Updated index.html and CSS for improved styling and layout consistency.
@@ -1,11 +1,32 @@
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.wrangler
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.vinxi
|
||||
app.config.timestamp_*.js
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
*.launch
|
||||
.settings/
|
||||
|
||||
# Temp
|
||||
gitignore
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
node_modules/.tmp
|
||||
|
||||
# Generated by scripts/copy-canvaskit.mjs (postinstall)
|
||||
public/canvaskit.wasm
|
||||
Thumbs.db
|
||||
canvaskit.wasm
|
||||
|
||||
# Skills
|
||||
.claude
|
||||
|
||||
@@ -4,26 +4,27 @@
|
||||
"": {
|
||||
"name": "lottie-experiments",
|
||||
"dependencies": {
|
||||
"canvaskit-wasm": "^0.39.1",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@kobalte/core": "^0.13.11",
|
||||
"@solidjs/router": "^0.16.1",
|
||||
"canvaskit-wasm": "^0.41.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"lucide-solid": "^1.18.0",
|
||||
"shadcn": "^4.11.0",
|
||||
"solid-js": "^1.9.9",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vite-plugin-solid-svg": "^0.8.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-slider": "^1.2.2",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/node": "^22.19.19",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.0.7",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@types/node": "^25.9.2",
|
||||
"solid-devtools": "^0.34.3",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.4",
|
||||
"vite-plugin-solid": "^2.11.8",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -36,16 +37,28 @@
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
|
||||
|
||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
|
||||
|
||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
|
||||
|
||||
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
|
||||
|
||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
@@ -56,9 +69,15 @@
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="],
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="],
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA=="],
|
||||
|
||||
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="],
|
||||
|
||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-syntax-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw=="],
|
||||
|
||||
"@babel/preset-typescript": ["@babel/preset-typescript@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-syntax-jsx": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-typescript": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
|
||||
|
||||
@@ -66,57 +85,77 @@
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
||||
"@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
||||
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.71.3", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-WSmox5aD+XxJEUEOTk7gKLpd5+Iz9Nik89Zpbu5DijMln6LsFiv3xpNKBMc/b9sSkUlKvAblzrhik2TqKFE7NA=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
|
||||
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
|
||||
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
|
||||
|
||||
"@fontsource-variable/inter": ["@fontsource-variable/inter@5.2.8", "", {}, "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ=="],
|
||||
|
||||
"@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
|
||||
|
||||
"@internationalized/date": ["@internationalized/date@3.12.2", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw=="],
|
||||
|
||||
"@internationalized/number": ["@internationalized/number@3.6.7", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
@@ -128,35 +167,25 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
|
||||
"@kobalte/core": ["@kobalte/core@0.13.11", "", { "dependencies": { "@floating-ui/dom": "^1.5.1", "@internationalized/date": "^3.4.0", "@internationalized/number": "^3.2.1", "@kobalte/utils": "^0.9.1", "@solid-primitives/props": "^3.1.8", "@solid-primitives/resize-observer": "^2.0.26", "solid-presence": "^0.1.8", "solid-prevent-scroll": "^0.1.4" }, "peerDependencies": { "solid-js": "^1.8.15" } }, "sha512-hK7TYpdib/XDb/r/4XDBFaO9O+3ZHz4ZWryV4/3BfES+tSQVgg2IJupDnztKXB0BqbSRy/aWlHKw1SPtNPYCFQ=="],
|
||||
|
||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||
"@kobalte/utils": ["@kobalte/utils@0.9.1", "", { "dependencies": { "@solid-primitives/event-listener": "^2.2.14", "@solid-primitives/keyed": "^1.2.0", "@solid-primitives/map": "^0.4.7", "@solid-primitives/media": "^2.2.4", "@solid-primitives/props": "^3.1.8", "@solid-primitives/refs": "^1.0.5", "@solid-primitives/utils": "^6.2.1" }, "peerDependencies": { "solid-js": "^1.8.8" } }, "sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w=="],
|
||||
|
||||
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
|
||||
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
|
||||
|
||||
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
|
||||
|
||||
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||
"@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="],
|
||||
|
||||
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
|
||||
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||
|
||||
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="],
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
|
||||
|
||||
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
|
||||
|
||||
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="],
|
||||
|
||||
"@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
"@nothing-but/utils": ["@nothing-but/utils@0.17.0", "", {}, "sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.61.0", "", { "os": "android", "cpu": "arm" }, "sha512-dnxczajOqt0gesZlN5pGQ1s1imQVrsmCw5G2Ci4oM+0WvNz3pyRnlWrT7McoZIb8VlFwCawdmbWRmxRn7HI+VQ=="],
|
||||
|
||||
@@ -208,6 +237,48 @@
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.61.0", "", { "os": "win32", "cpu": "x64" }, "sha512-tkuFxhvKO/HlGd0VsINF6vHSYH8AF8W0TcNxKDK6JZmrehngFj78pToc8iemtnvwilDjs2G/qSzYFhe9U8q+fw=="],
|
||||
|
||||
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
|
||||
|
||||
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
|
||||
|
||||
"@solid-devtools/debugger": ["@solid-devtools/debugger@0.28.1", "", { "dependencies": { "@nothing-but/utils": "~0.17.0", "@solid-devtools/shared": "^0.20.0", "@solid-primitives/bounds": "^0.1.1", "@solid-primitives/event-listener": "^2.4.1", "@solid-primitives/keyboard": "^1.3.1", "@solid-primitives/rootless": "^1.5.1", "@solid-primitives/scheduled": "^1.5.1", "@solid-primitives/static-store": "^0.1.1", "@solid-primitives/utils": "^6.3.1" }, "peerDependencies": { "solid-js": "^1.9.0" } }, "sha512-6qIUI6VYkXoRnL8oF5bvh2KgH71qlJ18hNw/mwSyY6v48eb80ZR48/5PDXufUa3q+MBSuYa1uqTMwLewpay9eg=="],
|
||||
|
||||
"@solid-devtools/shared": ["@solid-devtools/shared@0.20.0", "", { "dependencies": { "@nothing-but/utils": "~0.17.0", "@solid-primitives/event-listener": "^2.4.1", "@solid-primitives/media": "^2.3.1", "@solid-primitives/refs": "^1.1.1", "@solid-primitives/rootless": "^1.5.1", "@solid-primitives/scheduled": "^1.5.1", "@solid-primitives/static-store": "^0.1.1", "@solid-primitives/styles": "^0.1.1", "@solid-primitives/utils": "^6.3.1" }, "peerDependencies": { "solid-js": "^1.9.0" } }, "sha512-o5TACmUOQsxpzpOKCjbQqGk8wL8PMi+frXG9WNu4Lh3PQVUB6hs95Kl/S8xc++zwcMguUKZJn8h5URUiMOca6Q=="],
|
||||
|
||||
"@solid-primitives/bounds": ["@solid-primitives/bounds@0.1.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/resize-observer": "^2.1.5", "@solid-primitives/static-store": "^0.1.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-JFym8zijMfWp1FaAmJlH3xMfenCuhjaUsoBn3kt9FtoWwLj+yt+EGYt+p3SkOKwF7h4gaGtZ5PIdSbSNVWkRmg=="],
|
||||
|
||||
"@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.5", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA=="],
|
||||
|
||||
"@solid-primitives/keyboard": ["@solid-primitives/keyboard@1.3.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-sav+l+PL+74z3yaftVs7qd8c2SXkqzuxPOVibUe5wYMt+U5Hxp3V3XCPgBPN2I6cANjvoFtz0NiU8uHVLdi9FQ=="],
|
||||
|
||||
"@solid-primitives/keyed": ["@solid-primitives/keyed@1.5.3", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA=="],
|
||||
|
||||
"@solid-primitives/map": ["@solid-primitives/map@0.4.13", "", { "dependencies": { "@solid-primitives/trigger": "^1.1.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew=="],
|
||||
|
||||
"@solid-primitives/media": ["@solid-primitives/media@2.3.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/static-store": "^0.1.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-LX9fB5WDaK87FMDtUB1qokBOfT2et9Uobv/zZaKLH9caFSz4+P70MBKEIBHcZQy+9MV5M2XvGYLTbLskjkzMjA=="],
|
||||
|
||||
"@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="],
|
||||
|
||||
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="],
|
||||
|
||||
"@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/static-store": "^0.1.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw=="],
|
||||
|
||||
"@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="],
|
||||
|
||||
"@solid-primitives/scheduled": ["@solid-primitives/scheduled@1.5.3", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-oNwLE6E6lxJAWrc8QXuwM0k2oU1BnANnkChwMw82aK1j3+mWGJkG1IFe5gCwbV+afYmjI76t9JJV3md/8tLw+g=="],
|
||||
|
||||
"@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ=="],
|
||||
|
||||
"@solid-primitives/styles": ["@solid-primitives/styles@0.1.3", "", { "dependencies": { "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-7YdA21prMeCX+oOF/1RAn02+cGz/pG4dyPWtHBC2H8aZvnC7IfThBt80mP+TioejrdfE7Lc54Uh18f7Pig+gRQ=="],
|
||||
|
||||
"@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng=="],
|
||||
|
||||
"@solid-primitives/utils": ["@solid-primitives/utils@6.4.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A=="],
|
||||
|
||||
"@solidjs/router": ["@solidjs/router@0.16.1", "", { "peerDependencies": { "solid-js": "^1.8.6" } }, "sha512-IhyjedgC6LRpw/8CPGGI89FrV+r0xTHzOl2c4CRyzYQ1bLepJxbVI1LLKvsavMWY5TRBRacV7hAeOhuTXkjiqg=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.0" } }, "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.0", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.0", "@tailwindcss/oxide-darwin-arm64": "4.3.0", "@tailwindcss/oxide-darwin-x64": "4.3.0", "@tailwindcss/oxide-freebsd-x64": "4.3.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", "@tailwindcss/oxide-linux-x64-musl": "4.3.0", "@tailwindcss/oxide-wasm32-wasi": "4.3.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg=="],
|
||||
@@ -238,6 +309,8 @@
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.3.0", "", { "dependencies": { "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "tailwindcss": "4.3.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw=="],
|
||||
|
||||
"@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||
@@ -248,62 +321,312 @@
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="],
|
||||
"@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="],
|
||||
|
||||
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
||||
|
||||
"@types/react": ["@types/react@18.3.30", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-3ek6mwJL5/VBewBcY4S66cqlCtK3qi4WIq37Z0m/NHw1hjhI7274Mx1qz/+ggSzyBCOEf7eHjBN6INjPAWYfYw=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "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" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
|
||||
|
||||
"@webgpu/types": ["@webgpu/types@0.1.21", "", {}, "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow=="],
|
||||
|
||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
|
||||
|
||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||
|
||||
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.7", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ=="],
|
||||
|
||||
"babel-preset-solid": ["babel-preset-solid@1.9.12", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.6" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.12" }, "optionalPeers": ["solid-js"] }, "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.33", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw=="],
|
||||
|
||||
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "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" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="],
|
||||
|
||||
"canvaskit-wasm": ["canvaskit-wasm@0.39.1", "", { "dependencies": { "@webgpu/types": "0.1.21" } }, "sha512-Gy3lCmhUdKq+8bvDrs9t8+qf7RvcjuQn+we7vTVVyqgOVO1UVfHpsnBxkTZw+R4ApEJ3D5fKySl9TU11hmjl/A=="],
|
||||
"canvaskit-wasm": ["canvaskit-wasm@0.41.1", "", { "dependencies": { "@webgpu/types": "0.1.21" } }, "sha512-gO2rNMIE0lOQ8MaarM4qNeq2zZAEknAObKP2XAKidMSAY8P5sURGdFkrx1TSdHuBFqTf5w35JqjuUzjibqkD5g=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||
|
||||
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="],
|
||||
|
||||
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
||||
|
||||
"content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
||||
|
||||
"cosmiconfig": ["cosmiconfig@9.0.2", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||
|
||||
"css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="],
|
||||
|
||||
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
|
||||
|
||||
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
|
||||
|
||||
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.366", "", {}, "sha512-OlRuhb688YTCzzU3gXPLn6nGyd+F+53INE1qaKKlu6kETErE8FYsyDh0XqXEU+uBRn0MpCzz2vfNwORhkap8qg=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.22.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.12", "", { "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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
|
||||
|
||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
||||
|
||||
"eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="],
|
||||
|
||||
"execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
|
||||
|
||||
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@8.5.2", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="],
|
||||
|
||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||
|
||||
"fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"fuzzysort": ["fuzzysort@3.1.0", "", {}, "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-own-enumerable-keys": ["get-own-enumerable-keys@1.0.0", "", {}, "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
|
||||
|
||||
"hono": ["hono@4.12.25", "", {}, "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ=="],
|
||||
|
||||
"html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="],
|
||||
|
||||
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
|
||||
|
||||
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
||||
|
||||
"is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="],
|
||||
|
||||
"is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
|
||||
|
||||
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
||||
|
||||
"is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="],
|
||||
|
||||
"is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
|
||||
|
||||
"isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
||||
|
||||
"jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
@@ -328,63 +651,271 @@
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.468.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA=="],
|
||||
"lucide-solid": ["lucide-solid@1.18.0", "", { "peerDependencies": { "solid-js": "^1.4.7" } }, "sha512-YnCvJ37Yk51utVZUs8Rc1LgvxWPk6a58QnW4PgQquxhOHzlo+fjlTSc4zLvzQbOp8IxhPK6tPeGHUD8avd0cvA=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="],
|
||||
|
||||
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="],
|
||||
|
||||
"npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||
|
||||
"ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||
|
||||
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
||||
|
||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
|
||||
|
||||
"postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="],
|
||||
|
||||
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
||||
"postcss-selector-parser": ["postcss-selector-parser@7.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg=="],
|
||||
|
||||
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
|
||||
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
|
||||
|
||||
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||
|
||||
"qs": ["qs@6.15.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||
|
||||
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||
|
||||
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rollup": ["rollup@4.61.0", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.61.0", "@rollup/rollup-android-arm64": "4.61.0", "@rollup/rollup-darwin-arm64": "4.61.0", "@rollup/rollup-darwin-x64": "4.61.0", "@rollup/rollup-freebsd-arm64": "4.61.0", "@rollup/rollup-freebsd-x64": "4.61.0", "@rollup/rollup-linux-arm-gnueabihf": "4.61.0", "@rollup/rollup-linux-arm-musleabihf": "4.61.0", "@rollup/rollup-linux-arm64-gnu": "4.61.0", "@rollup/rollup-linux-arm64-musl": "4.61.0", "@rollup/rollup-linux-loong64-gnu": "4.61.0", "@rollup/rollup-linux-loong64-musl": "4.61.0", "@rollup/rollup-linux-ppc64-gnu": "4.61.0", "@rollup/rollup-linux-ppc64-musl": "4.61.0", "@rollup/rollup-linux-riscv64-gnu": "4.61.0", "@rollup/rollup-linux-riscv64-musl": "4.61.0", "@rollup/rollup-linux-s390x-gnu": "4.61.0", "@rollup/rollup-linux-x64-gnu": "4.61.0", "@rollup/rollup-linux-x64-musl": "4.61.0", "@rollup/rollup-openbsd-x64": "4.61.0", "@rollup/rollup-openharmony-arm64": "4.61.0", "@rollup/rollup-win32-arm64-msvc": "4.61.0", "@rollup/rollup-win32-ia32-msvc": "4.61.0", "@rollup/rollup-win32-x64-gnu": "4.61.0", "@rollup/rollup-win32-x64-msvc": "4.61.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-T9mWdbWfQtp0B5lv/HX+wrhYsmXRlcWnXXmJbXqKJhlRaoS6KMhq0gpyzW4UJfclcxrEdLnTgjT2NjruLONu0g=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||
|
||||
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||
|
||||
"seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="],
|
||||
|
||||
"seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="],
|
||||
|
||||
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"shadcn": ["shadcn@4.11.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-UV0cchFea9hO7poV1CuEP0wvmYjpAqcxCKdy23bndl2Du2ARtDs8A4xdzfhUjDBeOW1nNpJ6lXmsEpsply2SfQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"solid-devtools": ["solid-devtools@0.34.5", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.6", "@solid-devtools/debugger": "^0.28.1", "@solid-devtools/shared": "^0.20.0" }, "peerDependencies": { "solid-js": "^1.9.0", "vite": "^2.2.3 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["vite"] }, "sha512-KNVdS9MQzzeVS++Vmg4JeU0fM6ZMuBEmkBA7SmqPS2s5UHpRjv1PNH8gShmlN9L/tki6OUAzJP3H1aKq2AcOSg=="],
|
||||
|
||||
"solid-js": ["solid-js@1.9.13", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-6hJeJMOcEX8ktqjpDoJZEmld3ijvcvWBDtiXBm7f4332SiFN66QeAQI1REQshvyUoISsSeJ4PHDauKYbwao9JQ=="],
|
||||
|
||||
"solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="],
|
||||
|
||||
"solid-prevent-scroll": ["solid-prevent-scroll@0.1.10", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw=="],
|
||||
|
||||
"solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@2.6.1", "", {}, "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ=="],
|
||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
||||
"strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
|
||||
|
||||
"svgo": ["svgo@3.3.3", "", { "dependencies": { "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.0.0", "sax": "^1.5.0" }, "bin": "./bin/svgo" }, "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.3.0", "", {}, "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q=="],
|
||||
|
||||
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="],
|
||||
|
||||
"tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
||||
|
||||
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
"type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||
|
||||
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"vite": ["vite@6.4.3", "", { "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" }, "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" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="],
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"vite": ["vite@7.3.5", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww=="],
|
||||
|
||||
"vite-plugin-solid": ["vite-plugin-solid@2.11.12", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA=="],
|
||||
|
||||
"vite-plugin-solid-svg": ["vite-plugin-solid-svg@0.8.1", "", { "dependencies": { "svgo": "^3.1.0" }, "peerDependencies": { "solid-js": "^1", "vite": ">=4" } }, "sha512-ROGC2ae1eYUCMd+zfJtsbUtuZwsb6DZS0+Sy5/ZXDokOunGi0Ez/cL7OPdsixN3I0/rNYd/3hilo3kpRMAS+IA=="],
|
||||
|
||||
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
"yocto-spinner": ["yocto-spinner@1.2.0", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw=="],
|
||||
|
||||
"@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||
|
||||
"@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||
|
||||
@@ -397,5 +928,47 @@
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||
|
||||
"svgo/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||
|
||||
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
|
||||
|
||||
"enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"style": "kobalte",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
@@ -10,6 +10,8 @@
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"rtl": false,
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
@@ -17,5 +19,9 @@
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
"menuColor": "default",
|
||||
"menuAccent": "subtle",
|
||||
"registries": {
|
||||
"@zaidan": "https://zaidan.carere.dev/r/{style}/{name}.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<title>Lottie · Skia CanvasKit</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/mark.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<title>Text-to-Lottie by Diffusion Studio</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
<script src="/src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,26 +11,26 @@
|
||||
"postinstall": "node scripts/copy-canvaskit.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"canvaskit-wasm": "^0.39.1",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@kobalte/core": "^0.13.11",
|
||||
"@solidjs/router": "^0.16.1",
|
||||
"canvaskit-wasm": "^0.41.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"motion": "^12.40.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwind-merge": "^2.6.0"
|
||||
"lucide-solid": "^1.18.0",
|
||||
"shadcn": "^4.11.0",
|
||||
"solid-js": "^1.9.9",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vite-plugin-solid-svg": "^0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-slider": "^1.2.2",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/node": "^22.19.19",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"typescript": "~5.7.2",
|
||||
"vite": "^6.0.7"
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@types/node": "^25.9.2",
|
||||
"solid-devtools": "^0.34.3",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.4",
|
||||
"vite-plugin-solid": "^2.11.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="120" height="120" rx="24" fill="#EC4021"/>
|
||||
<path d="M49.9226 20.6331C56.1585 17.1223 63.8415 17.1223 70.0774 20.6331L89.9226 31.8061C96.1585 35.3169 100 41.8053 100 48.827V71.1729C100 78.1947 96.1585 84.683 89.9226 88.1939L70.0774 99.3669C63.8415 102.878 56.1585 102.878 49.9226 99.3669L30.0774 88.1939C23.8415 84.683 20 78.1947 20 71.1729V48.827C20 41.8053 23.8415 35.3169 30.0774 31.8061L49.9226 20.6331ZM65.1437 33.151C62.0257 31.3956 58.1842 31.3956 55.0663 33.151L38.6891 42.3715C35.5711 44.1269 33.6504 47.3711 33.6504 50.8819V69.3228C33.6504 72.8336 35.5711 76.0778 38.6891 77.8333L55.0663 87.0537C58.1842 88.8091 62.0257 88.8091 65.1437 87.0537L81.5209 77.8333C84.6388 76.0778 86.5596 72.8336 86.5596 69.3228V50.8819C86.5596 47.3711 84.6388 44.1269 81.5209 42.3715L65.1437 33.151Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 925 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><circle cx="16" cy="16" r="6" fill="#56bcf6"/><circle cx="16" cy="16" r="12" fill="none" stroke="#262f49" stroke-width="2"/></svg>
|
||||
|
Before Width: | Height: | Size: 191 B |
@@ -1,197 +0,0 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import { PlaybackControls } from "@/components/PlaybackControls";
|
||||
import { PropertiesPanel, type ControlMeta } from "@/components/PropertiesPanel";
|
||||
import { ZoomControls } from "@/components/ZoomControls";
|
||||
import { LottiePlayer, type AnimationSlot } from "@/lib/lottie-player";
|
||||
|
||||
// The Lottie file lives in /public and is fetched at startup.
|
||||
const LOTTIE_URL = "/lottie.json";
|
||||
// Optional sidecar describing how to present the animation's slottable
|
||||
// properties (labels, slider ranges). Missing file = no properties panel.
|
||||
const CONTROLS_URL = "/controls.json";
|
||||
|
||||
/**
|
||||
* Startup playback options read from the URL query string. Together with the
|
||||
* `data-testid="lottie-canvas"` hook on the canvas, these let an automated
|
||||
* agent driving the browser pin the animation to an exact frame and screenshot
|
||||
* it deterministically — no console bridge or slider-dragging required.
|
||||
*
|
||||
* - `?frame=300` seeks to frame 300 and (because a pinned frame is meant to be
|
||||
* inspected) holds it paused.
|
||||
* - `?paused=1` starts paused; `?paused=0` forces autoplay even with a frame
|
||||
* pinned. Omitted, autoplay is on unless a frame is pinned.
|
||||
*/
|
||||
function readStartupOptions(search: string): { frame: number | null; paused: boolean } {
|
||||
const params = new URLSearchParams(search);
|
||||
|
||||
const rawFrame = params.get("frame");
|
||||
const parsed = rawFrame === null ? NaN : Number(rawFrame);
|
||||
const frame = Number.isFinite(parsed) ? parsed : null;
|
||||
|
||||
const rawPaused = params.get("paused");
|
||||
// A pinned frame defaults to paused; otherwise default to playing.
|
||||
const paused = rawPaused === null ? frame !== null : rawPaused !== "0";
|
||||
|
||||
return { frame, paused };
|
||||
}
|
||||
|
||||
async function loadControlsMeta(): Promise<Record<string, ControlMeta>> {
|
||||
try {
|
||||
const res = await fetch(CONTROLS_URL);
|
||||
if (!res.ok) return {};
|
||||
const data = (await res.json()) as { controls?: ControlMeta[] };
|
||||
return Object.fromEntries((data.controls ?? []).map((c) => [c.sid, c]));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the panel's current slot values into a copy of the original Lottie
|
||||
* JSON and triggers a download. Slot definitions live under the top-level
|
||||
* `slots` map keyed by sid; scalar/color/vec2 values sit at `p.k`, text at
|
||||
* `p.p.t` (see the write-lottie skill).
|
||||
*/
|
||||
function downloadConfiguredLottie(
|
||||
lottieJson: string,
|
||||
slots: AnimationSlot[],
|
||||
values: Record<string, AnimationSlot["value"]>
|
||||
) {
|
||||
const doc = JSON.parse(lottieJson) as {
|
||||
slots?: Record<string, { p?: { k?: unknown; p?: { t?: string } } }>;
|
||||
};
|
||||
|
||||
for (const slot of slots) {
|
||||
const def = doc.slots?.[slot.id]?.p;
|
||||
if (!def) continue;
|
||||
const value = values[slot.id] ?? slot.value;
|
||||
if (slot.type === "text") {
|
||||
if (def.p) def.p.t = value as string;
|
||||
} else {
|
||||
def.k = value;
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([JSON.stringify(doc)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "lottie.json";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const playerRef = useRef<LottiePlayer | null>(null);
|
||||
const lottieJsonRef = useRef<string>("");
|
||||
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [currentFrame, setCurrentFrame] = useState(0);
|
||||
const [totalFrames, setTotalFrames] = useState(0);
|
||||
const [fps, setFps] = useState(0);
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [slots, setSlots] = useState<AnimationSlot[]>([]);
|
||||
const [controlsMeta, setControlsMeta] = useState<Record<string, ControlMeta>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
let disposed = false;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(LOTTIE_URL);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load ${LOTTIE_URL} (HTTP ${res.status})`);
|
||||
}
|
||||
const [json, meta] = await Promise.all([res.text(), loadControlsMeta()]);
|
||||
if (disposed) return;
|
||||
lottieJsonRef.current = json;
|
||||
|
||||
const player = await LottiePlayer.create(canvas, json, {
|
||||
onFrame: (frame, total) => {
|
||||
setCurrentFrame(frame);
|
||||
setTotalFrames(total);
|
||||
},
|
||||
onPlayStateChange: setPlaying,
|
||||
onCameraChange: setZoom,
|
||||
});
|
||||
if (disposed) {
|
||||
player.dispose();
|
||||
return;
|
||||
}
|
||||
playerRef.current = player;
|
||||
setTotalFrames(player.getTotalFrames());
|
||||
setFps(player.getFps());
|
||||
setSlots(player.getSlots());
|
||||
setControlsMeta(meta);
|
||||
|
||||
const { frame, paused } = readStartupOptions(window.location.search);
|
||||
if (frame !== null) player.seek(frame);
|
||||
if (!paused) player.play();
|
||||
} catch (e) {
|
||||
if (!disposed) setError(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
})();
|
||||
|
||||
const observer = new ResizeObserver(() => playerRef.current?.resize());
|
||||
observer.observe(canvas);
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
observer.disconnect();
|
||||
playerRef.current?.dispose();
|
||||
playerRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="dark relative h-full w-full overflow-hidden bg-neutral-950">
|
||||
<canvas ref={canvasRef} data-testid="lottie-canvas" className="block h-full w-full" />
|
||||
|
||||
{error && (
|
||||
<div className="absolute inset-0 flex items-center justify-center p-6 text-center text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pointer-events-none absolute left-0 top-0 flex justify-end p-4 sm:p-6">
|
||||
<ZoomControls
|
||||
zoom={zoom}
|
||||
onZoomIn={() => playerRef.current?.zoomByCentered(1.2)}
|
||||
onZoomOut={() => playerRef.current?.zoomByCentered(1 / 1.2)}
|
||||
onReset={() => playerRef.current?.resetCamera()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pointer-events-none absolute right-0 top-0 flex max-h-full flex-col p-4 sm:p-6">
|
||||
<PropertiesPanel
|
||||
slots={slots}
|
||||
meta={controlsMeta}
|
||||
onScalar={(id, v) => playerRef.current?.setScalarSlot(id, v)}
|
||||
onColor={(id, rgba) => playerRef.current?.setColorSlot(id, rgba)}
|
||||
onVec2={(id, xy) => playerRef.current?.setVec2Slot(id, xy)}
|
||||
onText={(id, v) => playerRef.current?.setTextSlot(id, v)}
|
||||
onExport={(values) =>
|
||||
downloadConfiguredLottie(lottieJsonRef.current, slots, values)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex justify-center p-4 sm:p-6">
|
||||
<PlaybackControls
|
||||
playing={playing}
|
||||
currentFrame={currentFrame}
|
||||
totalFrames={totalFrames}
|
||||
fps={fps}
|
||||
onToggle={() => playerRef.current?.toggle()}
|
||||
onSeek={(frame) => playerRef.current?.seek(frame)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { CanvasProvider } from '@/context/canvas';
|
||||
import { SidebarLeft } from '@/components/sidebar-left';
|
||||
import { PlaybackControls } from '@/components/playback-controls';
|
||||
import { ScenesContainer } from '@/components/scenes-container';
|
||||
import { SidebarRight } from '@/components/sidebar-right';
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<div class="relative h-screen w-screen bg-canvas">
|
||||
<CanvasProvider>
|
||||
<SidebarLeft />
|
||||
<div class="absolute inset-x-0 bottom-4 flex flex-col items-center gap-4 px-4">
|
||||
<PlaybackControls />
|
||||
<ScenesContainer />
|
||||
</div>
|
||||
<SidebarRight />
|
||||
</CanvasProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.4745 10.6465C14.6697 10.4513 14.9863 10.4512 15.1815 10.6465C15.3766 10.8417 15.3767 11.1583 15.1815 11.3535L12.5301 14.0049C12.2372 14.2978 11.7615 14.2978 11.4686 14.0049L8.81724 11.3535C8.6221 11.1583 8.62213 10.8417 8.81724 10.6465C9.01248 10.4512 9.329 10.4513 9.52427 10.6465L11.9999 13.1211L14.4745 10.6465Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 431 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.7025 8.47849C15.9721 8.6548 16.0478 9.01632 15.8715 9.28596L11.9048 15.3526C11.8114 15.4956 11.6599 15.5904 11.4905 15.612C11.321 15.6337 11.1506 15.5799 11.0242 15.465L8.45754 13.1317C8.21916 12.915 8.2016 12.5461 8.41831 12.3077C8.63502 12.0693 9.00395 12.0517 9.24233 12.2684L11.3028 14.1416L14.8951 8.6475C15.0714 8.37786 15.4328 8.3022 15.7025 8.47849Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 514 B |
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="9.25" y="7" width="1.5" height="10" rx="0.6" fill="currentColor"/>
|
||||
<rect x="13.25" y="7" width="1.5" height="10" rx="0.6" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 233 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.25131 6.57463C8.40728 6.48059 8.60109 6.47507 8.76216 6.56008L18.1153 11.4965C18.2857 11.5864 18.3924 11.7633 18.3924 11.956C18.3924 12.1487 18.2857 12.3256 18.1153 12.4155L8.76216 17.3519C8.60109 17.4369 8.40728 17.4314 8.25131 17.3374C8.09535 17.2433 8 17.0744 8 16.8924V7.01962C8 6.8375 8.09535 6.66867 8.25131 6.57463ZM9.03924 7.88141V16.0305L16.7595 11.956L9.03924 7.88141Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.89171 5.0581C11.1135 4.3527 12.6189 4.3527 13.8407 5.0581L16.8918 6.81971C18.1136 7.52511 18.8662 8.82875 18.8662 10.2395V13.7626C18.8662 15.1733 18.1136 16.477 16.8918 17.1824L13.8407 18.944C12.6189 19.6494 11.1135 19.6494 9.89171 18.944L6.84061 17.1824C5.61887 16.477 4.86621 15.1733 4.86621 13.7626V10.2395C4.86621 8.82876 5.61884 7.52512 6.84061 6.81971L9.89171 5.0581ZM12.8729 6.79153C12.262 6.43888 11.5093 6.43887 10.8985 6.79153L7.85001 8.55172C7.23913 8.90443 6.86271 9.55625 6.86271 10.2616V13.7816C6.86271 14.487 7.23912 15.1388 7.85001 15.4915L10.8985 17.2515C11.5094 17.6042 12.262 17.6042 12.8729 17.2515L15.9213 15.4915C16.5322 15.1388 16.9086 14.487 16.9086 13.7816V10.2616C16.9086 9.55624 16.5322 8.90442 15.9213 8.55172L12.8729 6.79153Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 870 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.37891 6C9.90926 6.00006 10.4179 6.21092 10.793 6.58594L11.707 7.5H16.5C17.6046 7.5 18.5 8.39543 18.5 9.5V15C18.5 16.1046 17.6046 17 16.5 17H7.5C6.39543 17 5.5 16.1046 5.5 15V8C5.5 6.89543 6.39543 6 7.5 6H9.37891ZM10.793 9.41406C10.4179 9.78908 9.90926 9.99994 9.37891 10H6.5V15C6.5 15.5523 6.94772 16 7.5 16H16.5C17.0523 16 17.5 15.5523 17.5 15V9.5C17.5 8.94772 17.0523 8.5 16.5 8.5H11.707L10.793 9.41406ZM7.5 7C6.94772 7 6.5 7.44772 6.5 8V9H9.37891C9.64404 8.99994 9.89845 8.89451 10.0859 8.70703L10.793 8L10.0859 7.29297C9.89845 7.10549 9.64404 7.00006 9.37891 7H7.5Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 685 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 7.25C12.5 6.97386 12.2761 6.75 12 6.75C11.7239 6.75 11.5 6.97386 11.5 7.25V11.5H7.25C6.97386 11.5 6.75 11.7239 6.75 12C6.75 12.2761 6.97386 12.5 7.25 12.5H11.5V16.75C11.5 17.0261 11.7239 17.25 12 17.25C12.2761 17.25 12.5 17.0261 12.5 16.75V12.5H16.75C17.0261 12.5 17.25 12.2761 17.25 12C17.25 11.7239 17.0261 11.5 16.75 11.5H12.5V7.25Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 494 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.0713 6.54004C18.584 6.54004 18.9999 6.95602 19 7.46875V16.5254C19 17.0382 18.5841 17.4541 18.0713 17.4541H5.92871L5.83398 17.4492C5.36562 17.4018 5 17.0063 5 16.5254V7.46875C5.00014 6.98796 5.36567 6.59231 5.83398 6.54492L5.92871 6.54004H18.0713ZM10 16.5254H18.0713V7.46875H10V16.5254ZM5.92871 16.5254H9V7.46875H5.92871V16.5254Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 445 B |
@@ -1,62 +0,0 @@
|
||||
import { Pause, Play } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
|
||||
interface PlaybackControlsProps {
|
||||
playing: boolean;
|
||||
currentFrame: number;
|
||||
totalFrames: number;
|
||||
fps: number;
|
||||
onToggle: () => void;
|
||||
onSeek: (frame: number) => void;
|
||||
}
|
||||
|
||||
export function PlaybackControls({
|
||||
playing,
|
||||
currentFrame,
|
||||
totalFrames,
|
||||
fps,
|
||||
onToggle,
|
||||
onSeek,
|
||||
}: PlaybackControlsProps) {
|
||||
const max = Math.max(0, totalFrames - 1);
|
||||
const frame = Math.min(Math.round(currentFrame), max);
|
||||
const pad = String(max).length;
|
||||
|
||||
return (
|
||||
<Card className="pointer-events-auto w-full max-w-xl gap-0 py-2 backdrop-blur-md bg-neutral-900/90 border border-border/5 shadow-lg">
|
||||
<CardContent className="flex items-center p-0 h-full px-2 gap-3">
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={onToggle}
|
||||
aria-label={playing ? "Pause" : "Play"}
|
||||
className="size-7 rounded-sm"
|
||||
>
|
||||
{playing ? <Pause /> : <Play />}
|
||||
</Button>
|
||||
|
||||
<Slider
|
||||
className="flex-1"
|
||||
min={0}
|
||||
max={max || 1}
|
||||
step={1}
|
||||
value={[frame]}
|
||||
onValueChange={([v]) => onSeek(v)}
|
||||
aria-label="Seek"
|
||||
/>
|
||||
|
||||
<div className="shrink-0 text-right font-mono text-xs tabular-nums text-muted-foreground flex items-center gap-2">
|
||||
<span>
|
||||
<span className="text-foreground">{String(frame).padStart(pad, "0")}</span>
|
||||
{" / "}
|
||||
{max}
|
||||
</span>
|
||||
<div className="h-6 w-px bg-muted-foreground/15" />
|
||||
<span className="text-muted-foreground/70 pr-2">{fps}FPS</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Scrubber } from "@/components/ui/scrubber";
|
||||
import type { AnimationSlot } from "@/lib/lottie-player";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
/** Presentation metadata for a slot, loaded from /controls.json. */
|
||||
export interface ControlMeta {
|
||||
sid: string;
|
||||
label?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
export interface PropertiesPanelProps {
|
||||
slots: AnimationSlot[];
|
||||
/** sid → metadata, merged onto each slot for labels and slider ranges. */
|
||||
meta: Record<string, ControlMeta>;
|
||||
onScalar: (id: string, value: number) => void;
|
||||
onColor: (id: string, rgba: [number, number, number, number]) => void;
|
||||
onVec2: (id: string, xy: [number, number]) => void;
|
||||
onText: (id: string, value: string) => void;
|
||||
/** Download the animation with the panel's current values applied. */
|
||||
onExport: (values: Record<string, AnimationSlot["value"]>) => void;
|
||||
}
|
||||
|
||||
function labelFor(slot: AnimationSlot, meta?: ControlMeta): string {
|
||||
return meta?.label ?? slot.id;
|
||||
}
|
||||
|
||||
// Shared layout for the non-scrubber controls: a fixed-width plain-text label
|
||||
// column on the left, the field(s) filling the rest. The fixed LABEL width
|
||||
// keeps every control's field left-aligned down the panel.
|
||||
const ROW = "flex items-center gap-2";
|
||||
const LABEL = "w-24 shrink-0 truncate text-xs text-muted-foreground";
|
||||
const FIELD =
|
||||
"h-[34px] rounded-md bg-accent/70 px-3 text-xs text-foreground outline-none focus-visible:bg-accent/90";
|
||||
|
||||
// Color slots are RGBA 0..1; <input type="color"> works in #rrggbb hex, so we
|
||||
// convert at the boundary and carry the slot's alpha through untouched.
|
||||
function rgbToHex([r, g, b]: [number, number, number, number]): string {
|
||||
const h = (n: number) =>
|
||||
Math.round(Math.max(0, Math.min(1, n)) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, "0");
|
||||
return `#${h(r)}${h(g)}${h(b)}`;
|
||||
}
|
||||
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const n = parseInt(hex.slice(1), 16);
|
||||
return [(n >> 16) / 255, ((n >> 8) & 0xff) / 255, (n & 0xff) / 255];
|
||||
}
|
||||
|
||||
export function PropertiesPanel({
|
||||
slots,
|
||||
meta,
|
||||
onScalar,
|
||||
onColor,
|
||||
onVec2,
|
||||
onText,
|
||||
onExport,
|
||||
}: PropertiesPanelProps) {
|
||||
// The slot's own value (from the player) is the baseline; we only track the
|
||||
// user's edits here, falling back to the baseline for anything untouched.
|
||||
// (Seeding state from the slots prop would go stale, since slots arrive empty
|
||||
// on first render and populate once the player loads.)
|
||||
const [edits, setEdits] = useState<Record<string, AnimationSlot["value"]>>({});
|
||||
|
||||
const set = (id: string, value: AnimationSlot["value"]) =>
|
||||
setEdits((v) => ({ ...v, [id]: value }));
|
||||
const values = Object.fromEntries(
|
||||
slots.map((s) => [s.id, edits[s.id] ?? s.value])
|
||||
);
|
||||
|
||||
return (
|
||||
<Card className="pointer-events-auto w-72 max-h-full overflow-hidden gap-0 py-3 backdrop-blur-md bg-neutral-900/90 border border-border/5 shadow-lg">
|
||||
<CardContent className="flex min-h-0 flex-col gap-5 px-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-medium tracking-wide text-foreground">
|
||||
Properties
|
||||
</span>
|
||||
<Button
|
||||
className="text-xs h-7 px-2.5 rounded-md"
|
||||
onClick={() => onExport(values)}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{slots.length > 0 && (
|
||||
<div className="flex min-h-0 flex-col gap-3 overflow-y-auto">
|
||||
{slots.map((slot) => {
|
||||
const m = meta[slot.id];
|
||||
const label = labelFor(slot, m);
|
||||
|
||||
if (slot.type === "scalar") {
|
||||
const value = values[slot.id] as number;
|
||||
const min = m?.min ?? 0;
|
||||
const max = m?.max ?? 100;
|
||||
const step = m?.step ?? ((max - min) / 100 || 1);
|
||||
// Show enough decimals to reflect the step (e.g. 0.01 → 2).
|
||||
const decimals = Math.min(
|
||||
4,
|
||||
Math.max(0, -Math.floor(Math.log10(step)))
|
||||
);
|
||||
return (
|
||||
<Scrubber
|
||||
key={slot.id}
|
||||
label={label}
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
decimals={decimals}
|
||||
onValueChange={(v) => {
|
||||
set(slot.id, v);
|
||||
onScalar(slot.id, v);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (slot.type === "color") {
|
||||
const value = values[slot.id] as [number, number, number, number];
|
||||
const hex = rgbToHex(value);
|
||||
// Label column on the left (plain text), then a field holding the
|
||||
// swatch and its hex. The native picker is overlaid transparently
|
||||
// so the whole field opens it.
|
||||
return (
|
||||
<div key={slot.id} className={ROW}>
|
||||
<span className={LABEL}>{label}</span>
|
||||
<label className="relative flex h-[34px] flex-1 cursor-pointer select-none items-center gap-2 overflow-hidden rounded-md bg-accent/70 pl-[6px]">
|
||||
<span
|
||||
className="size-[22px] shrink-0 rounded-[4px]"
|
||||
style={{ backgroundColor: hex }}
|
||||
/>
|
||||
<span className="font-mono text-xs uppercase text-foreground">
|
||||
{hex}
|
||||
</span>
|
||||
<input
|
||||
type="color"
|
||||
value={hex}
|
||||
onChange={(e) => {
|
||||
const [r, g, b] = hexToRgb(e.target.value);
|
||||
const rgba: [number, number, number, number] = [r, g, b, value[3]];
|
||||
set(slot.id, rgba);
|
||||
onColor(slot.id, rgba);
|
||||
}}
|
||||
className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
|
||||
aria-label={label}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (slot.type === "vec2") {
|
||||
const value = values[slot.id] as [number, number];
|
||||
const update = (i: 0 | 1, n: number) => {
|
||||
const next: [number, number] = i === 0 ? [n, value[1]] : [value[0], n];
|
||||
set(slot.id, next);
|
||||
onVec2(slot.id, next);
|
||||
};
|
||||
return (
|
||||
<div key={slot.id} className={ROW}>
|
||||
<span className={LABEL}>{label}</span>
|
||||
<div className="flex flex-1 gap-2">
|
||||
{([0, 1] as const).map((i) => (
|
||||
<input
|
||||
key={i}
|
||||
type="number"
|
||||
step={m?.step ?? 1}
|
||||
value={value[i]}
|
||||
onChange={(e) => update(i, Number(e.target.value))}
|
||||
className={`${FIELD} w-0 flex-1 font-mono [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none`}
|
||||
style={{ fontVariantNumeric: "tabular-nums" }}
|
||||
aria-label={`${label} ${i === 0 ? "x" : "y"}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// text
|
||||
const value = values[slot.id] as string;
|
||||
return (
|
||||
<div key={slot.id} className={ROW}>
|
||||
<span className={LABEL}>{label}</span>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
set(slot.id, e.target.value);
|
||||
onText(slot.id, e.target.value);
|
||||
}}
|
||||
className={`${FIELD} min-w-0 flex-1`}
|
||||
aria-label={label}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Minus, Plus } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
|
||||
interface ZoomControlsProps {
|
||||
zoom: number;
|
||||
onZoomIn: () => void;
|
||||
onZoomOut: () => void;
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
export function ZoomControls({
|
||||
zoom,
|
||||
onZoomIn,
|
||||
onZoomOut,
|
||||
onReset,
|
||||
}: ZoomControlsProps) {
|
||||
return (
|
||||
<Card className="pointer-events-auto flex-row items-center gap-0.5 rounded-lg p-1 backdrop-blur-md bg-neutral-900/90 border border-border/5 shadow-lg">
|
||||
<Button variant="ghost" size="icon" className="size-7" onClick={onZoomOut} aria-label="Zoom out">
|
||||
<Minus />
|
||||
</Button>
|
||||
<button
|
||||
onClick={onReset}
|
||||
title="Reset view"
|
||||
className="min-w-14 rounded-md px-1 py-1 text-center font-mono text-xs tabular-nums text-muted-foreground transition-colors hover:text-foreground"
|
||||
>
|
||||
{Math.round(zoom * 100)}%
|
||||
</button>
|
||||
<Button variant="ghost" size="icon" className="size-7" onClick={onZoomIn} aria-label="Zoom in">
|
||||
<Plus />
|
||||
</Button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import { useCanvas } from "@/context/canvas";
|
||||
|
||||
export function PlaybackControls() {
|
||||
const { playing, currentFrame, totalFrames, fps, togglePlayback, seek } = useCanvas();
|
||||
|
||||
const max = () => Math.max(0, totalFrames() - 1);
|
||||
const frame = () => Math.min(Math.round(currentFrame()), max());
|
||||
const pad = () => String(max()).length;
|
||||
|
||||
return (
|
||||
<div class="flex justify-center p-1.5 rounded-2xl w-full max-w-xl gap-3 bg-background border border-border items-center">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
onClick={togglePlayback}
|
||||
>
|
||||
<Icon name={playing() ? "controls-pause" : "controls-play"} class="text-foreground" />
|
||||
</Button>
|
||||
|
||||
<Slider
|
||||
class="flex-1"
|
||||
minValue={0}
|
||||
maxValue={max() || 1}
|
||||
step={1}
|
||||
value={[frame()]}
|
||||
onChange={([v]) => seek(v)}
|
||||
/>
|
||||
|
||||
<span class="shrink-0 text-right font-mono text-xxs tabular-nums text-muted-foreground font-normal">
|
||||
<span class="text-foreground">{String(frame()).padStart(pad(), "0")}</span>
|
||||
{" / "}
|
||||
{max()}
|
||||
</span>
|
||||
<div class="h-5 w-px bg-border" />
|
||||
<span class="text-muted-foreground text-xxs font-mono font-normal pr-2">{fps()}FPS</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { For } from "solid-js";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
|
||||
type Scene = {
|
||||
label: string;
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
const scenes: Scene[] = [
|
||||
{ label: "Scene 1" },
|
||||
{ label: "Scene 2", active: true },
|
||||
{ label: "Scene 3" },
|
||||
{ label: "Scene 4" },
|
||||
];
|
||||
|
||||
export function ScenesContainer() {
|
||||
return (
|
||||
<div class="flex items-start justify-center gap-4">
|
||||
<For each={scenes}>
|
||||
{(scene) => (
|
||||
<div class="flex flex-col gap-2 w-[114px] shrink-0">
|
||||
<div
|
||||
class="aspect-video w-full overflow-hidden rounded-md bg-background"
|
||||
classList={{
|
||||
"border border-border": !scene.active,
|
||||
"border-2 border-primary": scene.active,
|
||||
}}
|
||||
/>
|
||||
<span class="text-center text-xxs text-muted-foreground">{scene.label}</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-16 w-[114px] shrink-0 items-center justify-center overflow-hidden rounded-md border border-border bg-background text-muted-foreground"
|
||||
>
|
||||
<Icon name="plus-add" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import { Button } from "./ui/button";
|
||||
|
||||
export function SidebarLeft() {
|
||||
return (
|
||||
<div class="absolute left-4 top-4 flex flex-col w-[236px] max-h-full rounded-2xl gap-0 bg-background border border-border text-muted-foreground">
|
||||
<div class="flex items-center justify-between h-12 px-3">
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<Icon name="diffusion-logo" class="size-6" />
|
||||
<span class="text-xxs font-strong">Diffusion Studio</span>
|
||||
</div>
|
||||
<Button size="icon" variant="ghost">
|
||||
<Icon name="sidebar" />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="px-4 py-3 border-t border-border flex flex-col">
|
||||
<div class="flex items-center justify-between h-8">
|
||||
<span class="text-xxs text-muted-foreground px-1">Projects</span>
|
||||
<Button size="icon" variant="ghost" class="text-muted-foreground">
|
||||
<Icon name="plus-add" />
|
||||
</Button>
|
||||
</div>
|
||||
<button class="flex items-center justify-between h-7 bg-muted rounded-md px-0.5 gap-1.5 my-0.5 flex-1 text-foreground">
|
||||
<Icon name="folder" />
|
||||
<span class="text-xxs">Main Project</span>
|
||||
<Icon name="confirm-check" class="ml-auto" />
|
||||
</button>
|
||||
<button class="flex items-center h-7 rounded-md px-0.5 gap-1.5 my-0.5 flex-1 text-muted-foreground">
|
||||
<Icon name="folder" />
|
||||
<span class="text-xxs">GitHub Banner</span>
|
||||
</button>
|
||||
<button class="flex items-center h-7 rounded-md px-0.5 gap-1.5 my-0.5 flex-1 text-muted-foreground">
|
||||
<Icon name="folder" />
|
||||
<span class="text-xxs">Instagram Reels</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { createSignal, For, Show, type JSX } from "solid-js";
|
||||
import { useCanvas } from "@/context/canvas";
|
||||
import { NumericSlider } from "@/components/ui/numeric-slider";
|
||||
import { Icon } from "@/components/ui/icon";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
|
||||
import type { AnimationSlot } from "@/types";
|
||||
|
||||
|
||||
// Color slots are RGBA 0..1; <input type="color"> works in #rrggbb hex, so we
|
||||
// convert at the boundary and carry the slot's alpha through untouched.
|
||||
function rgbToHex([r, g, b]: [number, number, number, number]): string {
|
||||
const h = (n: number) =>
|
||||
Math.round(Math.max(0, Math.min(1, n)) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, "0");
|
||||
return `#${h(r)}${h(g)}${h(b)}`;
|
||||
}
|
||||
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const n = parseInt(hex.slice(1), 16);
|
||||
return [(n >> 16) / 255, ((n >> 8) & 0xff) / 255, (n & 0xff) / 255];
|
||||
}
|
||||
|
||||
export function SidebarRight() {
|
||||
const { slots, zoom, controls, setScalarSlot, setColorSlot, setVec2Slot, setTextSlot, zoomByCentered, resetCamera } = useCanvas();
|
||||
const [edits, setEdits] = createSignal<Record<string, AnimationSlot["value"]>>({});
|
||||
|
||||
const set = (id: string, value: AnimationSlot["value"]) =>
|
||||
setEdits((v) => ({ ...v, [id]: value }));
|
||||
|
||||
const valueOf = (s: AnimationSlot) => edits()[s.id] ?? s.value;
|
||||
|
||||
const handleExport = async () => {
|
||||
const res = await fetch('/lottie.json');
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load /lottie.json (HTTP ${res.status})`);
|
||||
}
|
||||
|
||||
const values = Object.fromEntries(slots().map((s) => [s.id, valueOf(s)]));
|
||||
downloadConfiguredLottie(await res.text(), slots(), values);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="absolute right-4 top-4 flex flex-col w-[236px] max-h-full rounded-2xl gap-0 bg-background border border-border">
|
||||
<div class="flex items-center justify-between h-12 px-3 pl-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger class="flex items-center text-muted-foreground hover:text-foreground">
|
||||
<span class="text-xxs">
|
||||
{Math.round(zoom() * 100)}%
|
||||
</span>
|
||||
<Icon name="chevron-down" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onSelect={() => zoomByCentered(1.2)}>
|
||||
Zoom in
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => zoomByCentered(1 / 1.2)}>
|
||||
Zoom out
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSelect={() => zoomByCentered(1 / zoom())}>
|
||||
Zoom to 100%
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => resetCamera()}>
|
||||
Zoom to fit
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button onClick={handleExport}>
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Show when={slots().length > 0}>
|
||||
<Group title="Properties">
|
||||
<For each={slots()}>
|
||||
{(slot) => {
|
||||
const m = controls()?.[slot.id];
|
||||
const label = m?.label ?? slot.id;
|
||||
|
||||
if (slot.type === "scalar") {
|
||||
|
||||
return (
|
||||
<Slot label={label}>
|
||||
|
||||
<NumericSlider
|
||||
value={valueOf(slot) as number}
|
||||
minValue={m?.min ?? 0}
|
||||
maxValue={m?.max ?? 100}
|
||||
step={m?.step ?? (((m?.max ?? 100) - (m?.min ?? 0)) / 100 || 1)}
|
||||
onChange={(v) => {
|
||||
set(slot.id, v);
|
||||
setScalarSlot(slot.id, v);
|
||||
}}
|
||||
/>
|
||||
</Slot>
|
||||
);
|
||||
}
|
||||
|
||||
if (slot.type === "color") {
|
||||
const value = valueOf(slot) as [number, number, number, number];
|
||||
const hex = rgbToHex(value);
|
||||
// Label column on the left (plain text), then a field holding the
|
||||
// swatch and its hex. The native picker is overlaid transparently
|
||||
// so the whole field opens it.
|
||||
return (
|
||||
<Slot label={label}>
|
||||
<label class="relative flex h-7 flex-1 cursor-pointer select-none items-center gap-2 overflow-hidden rounded-md bg-input p-1 focus-ring">
|
||||
<span
|
||||
class="size-5 shrink-0 rounded-sm"
|
||||
style={{ "background-color": hex }}
|
||||
/>
|
||||
<span class="text-xxs uppercase text-foreground">
|
||||
{hex.replace("#", "")}
|
||||
</span>
|
||||
<input
|
||||
type="color"
|
||||
value={hex}
|
||||
onChange={(e) => {
|
||||
const [r, g, b] = hexToRgb(e.target.value);
|
||||
const rgba: [number, number, number, number] = [r, g, b, value[3]];
|
||||
set(slot.id, rgba);
|
||||
setColorSlot(slot.id, rgba);
|
||||
}}
|
||||
class="absolute inset-0 h-full w-full cursor-pointer opacity-0"
|
||||
aria-label={label}
|
||||
/>
|
||||
</label>
|
||||
</Slot>
|
||||
);
|
||||
}
|
||||
|
||||
if (slot.type === "vec2") {
|
||||
const value = valueOf(slot) as [number, number];
|
||||
const update = (i: 0 | 1, n: number) => {
|
||||
const next: [number, number] = i === 0 ? [n, value[1]] : [value[0], n];
|
||||
set(slot.id, next);
|
||||
setVec2Slot(slot.id, next);
|
||||
};
|
||||
return (
|
||||
<Slot label={label}>
|
||||
<div class="flex flex-1 gap-2">
|
||||
{([0, 1] as const).map((i) => (
|
||||
<input
|
||||
type="number"
|
||||
step={m?.step ?? 1}
|
||||
value={value[i]}
|
||||
onChange={(e) => update(i, Number(e.target.value))}
|
||||
class="rounded-md bg-input font-sans text-foreground outline-none w-0 flex-1 text-xxs h-7 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none px-2 focus-ring"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Slot>
|
||||
);
|
||||
}
|
||||
|
||||
// text
|
||||
const value = valueOf(slot) as string;
|
||||
return (
|
||||
<Slot label={label}>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
set(slot.id, e.target.value);
|
||||
setTextSlot(slot.id, e.target.value);
|
||||
}}
|
||||
class="rounded-md bg-input font-sans text-foreground outline-none w-0 flex-1 text-xxs h-7 px-2 focus-ring"
|
||||
aria-label={label}
|
||||
/>
|
||||
</Slot>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</Group>
|
||||
<div class="h-1 w-full" />
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function downloadConfiguredLottie(
|
||||
lottieJson: string,
|
||||
slots: AnimationSlot[],
|
||||
values: Record<string, AnimationSlot["value"]>
|
||||
) {
|
||||
const doc = JSON.parse(lottieJson) as {
|
||||
slots?: Record<string, { p?: { k?: unknown; p?: { t?: string } } }>;
|
||||
};
|
||||
|
||||
for (const slot of slots) {
|
||||
const def = doc.slots?.[slot.id]?.p;
|
||||
if (!def) continue;
|
||||
const value = values[slot.id] ?? slot.value;
|
||||
if (slot.type === "text") {
|
||||
if (def.p) def.p.t = value as string;
|
||||
} else {
|
||||
def.k = value;
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([JSON.stringify(doc)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "lottie.json";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
type SlotProps = {
|
||||
label?: string;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
function Slot(props: SlotProps) {
|
||||
return (
|
||||
<div class="flex items-center gap-1 min-h-7">
|
||||
<span class="w-20 shrink-0 truncate text-xxs text-muted-foreground">{props.label}</span>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type GroupProps = {
|
||||
title?: string;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
function Group(props: GroupProps) {
|
||||
return (
|
||||
<div class="flex flex-col border-t border-border px-4">
|
||||
<div class="h-11 flex items-center">
|
||||
<span class="text-xxs text-foreground">{props.title}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 pb-3">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,55 +1,52 @@
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { type ButtonRootProps, Root } from "@kobalte/core/button";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { type ComponentProps, splitProps, type ValidComponent } from "solid-js";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
||||
"group/button z-button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap outline-none transition-all active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
outline:
|
||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
||||
default: "z-button-variant-default",
|
||||
outline: "z-button-variant-outline",
|
||||
secondary: "z-button-variant-secondary",
|
||||
ghost: "z-button-variant-ghost",
|
||||
destructive: "z-button-variant-destructive",
|
||||
link: "z-button-variant-link",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3",
|
||||
lg: "h-10 rounded-md px-6",
|
||||
icon: "size-9",
|
||||
default: "z-button-size-default",
|
||||
xs: "z-button-size-xs",
|
||||
sm: "z-button-size-sm",
|
||||
lg: "z-button-size-lg",
|
||||
icon: "z-button-size-icon",
|
||||
"icon-xs": "z-button-size-icon-xs",
|
||||
"icon-sm": "z-button-size-icon-sm",
|
||||
"icon-lg": "z-button-size-icon-lg",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean;
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
type ButtonProps<T extends ValidComponent = "button"> = PolymorphicProps<T, ButtonRootProps<T>> &
|
||||
VariantProps<typeof buttonVariants> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const Button = <T extends ValidComponent = "button">(props: ButtonProps<T>) => {
|
||||
const [local, others] = splitProps(props as ButtonProps, ["variant", "size", "class"]);
|
||||
return (
|
||||
<Comp
|
||||
<Root
|
||||
class={cn(buttonVariants({ variant: local.variant, size: local.size }), local.class)}
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export { Button, buttonVariants };
|
||||
export { Button, type ButtonProps, buttonVariants };
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-content"
|
||||
className={cn("px-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Card, CardContent };
|
||||
@@ -0,0 +1,316 @@
|
||||
import * as DropdownMenuPrimitive from "@kobalte/core/dropdown-menu";
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import { Check, ChevronRight } from "lucide-solid";
|
||||
import type { ComponentProps, ValidComponent } from "solid-js";
|
||||
import { mergeProps, splitProps } from "solid-js";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type DropdownMenuProps = DropdownMenuPrimitive.DropdownMenuRootProps;
|
||||
|
||||
const DropdownMenu = (props: DropdownMenuProps) => {
|
||||
const mergedProps = mergeProps({ gutter: 4 }, props);
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...mergedProps} />;
|
||||
};
|
||||
|
||||
type DropdownMenuPortalProps = DropdownMenuPrimitive.DropdownMenuPortalProps;
|
||||
|
||||
const DropdownMenuPortal = (props: DropdownMenuPortalProps) => {
|
||||
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
||||
};
|
||||
|
||||
type DropdownMenuTriggerProps<T extends ValidComponent = "button"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuTriggerProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const DropdownMenuTrigger = <T extends ValidComponent = "button">(
|
||||
props: DropdownMenuTriggerProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuTriggerProps, ["class"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
class={local.class}
|
||||
data-slot="dropdown-menu-trigger"
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuContentProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuContentProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const DropdownMenuContent = <T extends ValidComponent = "div">(
|
||||
props: DropdownMenuContentProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuContentProps, ["class"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
class={cn(
|
||||
"z-50 z-dropdown-menu-content z-menu-target max-h-(--kb-popper-available-height) min-w-32 origin-(--kb-menu-content-transform-origin) overflow-y-auto overflow-x-hidden outline-none data-closed:overflow-hidden",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuGroupProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuGroupProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const DropdownMenuGroup = <T extends ValidComponent = "div">(props: DropdownMenuGroupProps<T>) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuGroupProps, ["class"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group class={local.class} data-slot="dropdown-menu-group" {...others} />
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuLabelProps<T extends ValidComponent = "span"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuGroupLabelProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class"> & {
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
const DropdownMenuLabel = <T extends ValidComponent = "span">(props: DropdownMenuLabelProps<T>) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuLabelProps, ["class", "inset"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.GroupLabel
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={local.inset}
|
||||
class={cn("z-dropdown-menu-label data-inset:pl-8", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuItemProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuItemProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class"> & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "destructive";
|
||||
};
|
||||
|
||||
const DropdownMenuItem = <T extends ValidComponent = "div">(rawProps: DropdownMenuItemProps<T>) => {
|
||||
const props = mergeProps({ variant: "default" } as DropdownMenuItemProps<T>, rawProps);
|
||||
const [local, others] = splitProps(props as DropdownMenuItemProps, ["class", "inset", "variant"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={local.inset}
|
||||
data-variant={local.variant}
|
||||
class={cn(
|
||||
"group/dropdown-menu-item relative z-dropdown-menu-item flex cursor-default select-none items-center outline-hidden data-disabled:pointer-events-none data-inset:pl-8 data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuSubProps = DropdownMenuPrimitive.DropdownMenuSubProps;
|
||||
|
||||
const DropdownMenuSub = (props: DropdownMenuSubProps) => {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
||||
};
|
||||
|
||||
type DropdownMenuSubTriggerProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuSubTriggerProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class" | "children"> & {
|
||||
inset?: boolean;
|
||||
};
|
||||
|
||||
const DropdownMenuSubTrigger = <T extends ValidComponent = "div">(
|
||||
props: DropdownMenuSubTriggerProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuSubTriggerProps, [
|
||||
"class",
|
||||
"inset",
|
||||
"children",
|
||||
]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={local.inset}
|
||||
class={cn(
|
||||
"z-dropdown-menu-sub-trigger flex cursor-default select-none items-center outline-hidden data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
<ChevronRight class="ml-auto" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuSubContentProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuSubContentProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const DropdownMenuSubContent = <T extends ValidComponent = "div">(
|
||||
props: DropdownMenuSubContentProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuSubContentProps, ["class"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
class={cn(
|
||||
"z-50 z-dropdown-menu-sub-content z-menu-target max-h-(--kb-popper-available-height) min-w-32 origin-(--kb-menu-content-transform-origin) overflow-y-auto overflow-x-hidden outline-none data-closed:overflow-hidden",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuCheckboxItemProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuCheckboxItemProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class" | "children">;
|
||||
|
||||
const DropdownMenuCheckboxItem = <T extends ValidComponent = "div">(
|
||||
props: DropdownMenuCheckboxItemProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuCheckboxItemProps, ["class", "children"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
class={cn(
|
||||
"relative z-dropdown-menu-checkbox-item flex cursor-default select-none items-center outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none z-dropdown-menu-item-indicator"
|
||||
data-slot="dropdown-menu-checkbox-item-indicator"
|
||||
>
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{local.children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuRadioGroupProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuRadioGroupProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const DropdownMenuRadioGroup = <T extends ValidComponent = "div">(
|
||||
props: DropdownMenuRadioGroupProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuRadioGroupProps, ["class"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
class={local.class}
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuRadioItemProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuRadioItemProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class" | "children">;
|
||||
|
||||
const DropdownMenuRadioItem = <T extends ValidComponent = "div">(
|
||||
props: DropdownMenuRadioItemProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuRadioItemProps, ["class", "children"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
class={cn(
|
||||
"relative z-dropdown-menu-radio-item flex cursor-default select-none items-center outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none z-dropdown-menu-item-indicator"
|
||||
data-slot="dropdown-menu-radio-item-indicator"
|
||||
>
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{local.children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuSeparatorProps<T extends ValidComponent = "hr"> = PolymorphicProps<
|
||||
T,
|
||||
DropdownMenuPrimitive.DropdownMenuSeparatorProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const DropdownMenuSeparator = <T extends ValidComponent = "hr">(
|
||||
props: DropdownMenuSeparatorProps<T>,
|
||||
) => {
|
||||
const [local, others] = splitProps(props as DropdownMenuSeparatorProps, ["class"]);
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("z-dropdown-menu-separator", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type DropdownMenuShortcutProps = ComponentProps<"span">;
|
||||
|
||||
const DropdownMenuShortcut = (props: DropdownMenuShortcutProps) => {
|
||||
const [local, others] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
class={cn("z-dropdown-menu-shortcut", local.class)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { cn } from "../../lib/utils";
|
||||
import type { Component, JSX } from "solid-js";
|
||||
|
||||
// Eagerly load all icons as components
|
||||
const icons = import.meta.glob<{ default: Component<JSX.SvgSVGAttributes<SVGSVGElement>> }>(
|
||||
'/src/assets/icons/*.svg',
|
||||
{ eager: true }
|
||||
);
|
||||
|
||||
type IconProps = {
|
||||
name: string;
|
||||
class?: string
|
||||
}
|
||||
|
||||
export function Icon(props: IconProps) {
|
||||
const getIcon = () => {
|
||||
const path = `/src/assets/icons/${props.name}.svg`;
|
||||
return icons[path]?.default;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{(() => {
|
||||
const IconComponent = getIcon();
|
||||
return IconComponent ? <IconComponent class={cn("size-6 shrink-0", props.class)} /> : null;
|
||||
})()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
data-slot="input"
|
||||
className={cn(
|
||||
"flex h-8 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-[color,box-shadow] outline-none",
|
||||
"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Input };
|
||||
@@ -0,0 +1,282 @@
|
||||
import { Show, createEffect, createSignal, untrack, type JSX } from "solid-js";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type NumericSliderProps = {
|
||||
value: number;
|
||||
onChange?(value: number): void;
|
||||
onDragStart?(value: number): void;
|
||||
onDragEnd?(value: number): void;
|
||||
minValue?: number;
|
||||
maxValue?: number;
|
||||
step?: number;
|
||||
format?(value: number): string;
|
||||
class?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const TRACK_INSET_PX = 8;
|
||||
const DRAG_THRESHOLD = 4;
|
||||
|
||||
const parseNumber = (input: string) => {
|
||||
const parsed = Number.parseFloat(input.replace(/[^0-9.-]/g, ""));
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
};
|
||||
|
||||
const defaultFormat = (value: number) => String(Math.round(value));
|
||||
|
||||
export function NumericSlider(props: NumericSliderProps) {
|
||||
const min = () => props.minValue ?? 0;
|
||||
const max = () => props.maxValue ?? 100;
|
||||
const step = () => {
|
||||
const raw = props.step ?? 1;
|
||||
return Number.isFinite(raw) && raw > 0 ? raw : 1;
|
||||
};
|
||||
const range = () => max() - min();
|
||||
const safeRange = () => (range() === 0 ? 1 : range());
|
||||
const clamp = (value: number) => Math.max(min(), Math.min(max(), value));
|
||||
const currentValue = () => clamp(props.value);
|
||||
const formatValue = (value: number) => props.format?.(value) ?? defaultFormat(value);
|
||||
|
||||
const [internal, setInternal] = createSignal(untrack(() => formatValue(currentValue())));
|
||||
const [displayValue, setDisplayValue] = createSignal(untrack(() => currentValue()));
|
||||
const [isEditing, setIsEditing] = createSignal(false);
|
||||
const fillRatio = () => (clamp(displayValue()) - min()) / safeRange();
|
||||
|
||||
let lastEmittedValue = untrack(() => currentValue());
|
||||
let containerEl: HTMLDivElement | undefined;
|
||||
let inputEl: HTMLInputElement | undefined;
|
||||
let dragRect: DOMRect | undefined;
|
||||
let pointerStart: { x: number; y: number } | null = null;
|
||||
let pointerStartTarget: HTMLElement | null = null;
|
||||
let activePointerId: number | null = null;
|
||||
let dragStarted = false;
|
||||
let skipBlurCommit = false;
|
||||
|
||||
createEffect(() => {
|
||||
const nextValue = currentValue();
|
||||
if (nextValue !== untrack(displayValue)) setDisplayValue(nextValue);
|
||||
|
||||
if (!untrack(isEditing)) {
|
||||
const nextLabel = formatValue(nextValue);
|
||||
if (nextLabel !== untrack(internal)) setInternal(nextLabel);
|
||||
}
|
||||
|
||||
lastEmittedValue = nextValue;
|
||||
});
|
||||
|
||||
const emitChange = (nextValue: number) => {
|
||||
const clamped = clamp(nextValue);
|
||||
lastEmittedValue = clamped;
|
||||
setDisplayValue(clamped);
|
||||
props.onChange?.(clamped);
|
||||
|
||||
if (!untrack(isEditing)) {
|
||||
setInternal(formatValue(clamped));
|
||||
}
|
||||
};
|
||||
|
||||
const valueFromClientX = (clientX: number) => {
|
||||
const rect = dragRect ?? containerEl?.getBoundingClientRect();
|
||||
if (!rect) return untrack(displayValue);
|
||||
|
||||
const left = rect.left + TRACK_INSET_PX;
|
||||
const width = rect.width - TRACK_INSET_PX;
|
||||
if (width <= 0) return min();
|
||||
|
||||
const ratio = (clientX - left) / width;
|
||||
const clampedRatio = Math.max(0, Math.min(1, ratio));
|
||||
const rawValue = min() + clampedRatio * range();
|
||||
const sliderStep = step();
|
||||
const snapped = Math.round(rawValue / sliderStep) * sliderStep;
|
||||
const stepDecimals = (sliderStep.toString().split(".")[1] || "").length;
|
||||
const normalized = stepDecimals === 0 ? snapped : Number(snapped.toFixed(stepDecimals));
|
||||
return clamp(normalized);
|
||||
};
|
||||
|
||||
const resetPointerState = () => {
|
||||
pointerStart = null;
|
||||
activePointerId = null;
|
||||
dragRect = undefined;
|
||||
dragStarted = false;
|
||||
};
|
||||
|
||||
const handlePointerMove = (event: PointerEvent) => {
|
||||
if (activePointerId === null || event.pointerId !== activePointerId) return;
|
||||
if (!pointerStart) return;
|
||||
|
||||
const dx = event.clientX - pointerStart.x;
|
||||
const dy = event.clientY - pointerStart.y;
|
||||
|
||||
if (!dragStarted && Math.hypot(dx, dy) >= DRAG_THRESHOLD) {
|
||||
dragStarted = true;
|
||||
if (inputEl && document.activeElement === inputEl) {
|
||||
inputEl.blur();
|
||||
}
|
||||
const startValue = valueFromClientX(event.clientX);
|
||||
emitChange(startValue);
|
||||
props.onDragStart?.(startValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dragStarted) {
|
||||
emitChange(valueFromClientX(event.clientX));
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointerUp = (event: PointerEvent) => {
|
||||
if (activePointerId === null || event.pointerId !== activePointerId) return;
|
||||
|
||||
const wasDragging = dragStarted;
|
||||
const endX = event.clientX;
|
||||
const startTarget = pointerStartTarget;
|
||||
|
||||
window.removeEventListener("pointermove", handlePointerMove);
|
||||
window.removeEventListener("pointerup", handlePointerUp);
|
||||
window.removeEventListener("pointercancel", handlePointerUp);
|
||||
|
||||
if (wasDragging) {
|
||||
const finalValue = valueFromClientX(endX);
|
||||
if (finalValue !== lastEmittedValue) emitChange(finalValue);
|
||||
resetPointerState();
|
||||
pointerStartTarget = null;
|
||||
props.onDragEnd?.(finalValue);
|
||||
return;
|
||||
}
|
||||
|
||||
resetPointerState();
|
||||
pointerStartTarget = null;
|
||||
|
||||
if (startTarget && containerEl?.contains(startTarget)) {
|
||||
if (startTarget instanceof HTMLInputElement) {
|
||||
startTarget.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const interactive = startTarget.closest(
|
||||
"button, [role='button'], a, input, select, textarea",
|
||||
);
|
||||
if (
|
||||
interactive instanceof HTMLElement &&
|
||||
interactive !== containerEl &&
|
||||
containerEl.contains(interactive)
|
||||
) {
|
||||
interactive.dispatchEvent(
|
||||
new MouseEvent("click", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
view: window,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointerDown = (event: PointerEvent) => {
|
||||
if (props.disabled ?? false) return;
|
||||
event.preventDefault();
|
||||
|
||||
dragRect = containerEl?.getBoundingClientRect();
|
||||
pointerStart = { x: event.clientX, y: event.clientY };
|
||||
pointerStartTarget = event.target instanceof HTMLElement ? event.target : null;
|
||||
activePointerId = event.pointerId;
|
||||
dragStarted = false;
|
||||
containerEl?.setPointerCapture?.(event.pointerId);
|
||||
|
||||
window.addEventListener("pointermove", handlePointerMove);
|
||||
window.addEventListener("pointerup", handlePointerUp);
|
||||
window.addEventListener("pointercancel", handlePointerUp);
|
||||
};
|
||||
|
||||
const handleInputFocus = (event: FocusEvent & { currentTarget: HTMLInputElement }) => {
|
||||
setIsEditing(true);
|
||||
event.currentTarget.select();
|
||||
};
|
||||
|
||||
const handleInputBlur = (event: FocusEvent & { currentTarget: HTMLInputElement }) => {
|
||||
setIsEditing(false);
|
||||
|
||||
if (skipBlurCommit) {
|
||||
skipBlurCommit = false;
|
||||
setInternal(formatValue(currentValue()));
|
||||
setDisplayValue(currentValue());
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = parseNumber(event.currentTarget.value);
|
||||
if (parsed !== undefined) {
|
||||
const clamped = clamp(parsed);
|
||||
emitChange(clamped);
|
||||
props.onDragEnd?.(clamped);
|
||||
return;
|
||||
}
|
||||
|
||||
setInternal(formatValue(currentValue()));
|
||||
setDisplayValue(currentValue());
|
||||
};
|
||||
|
||||
const handleInputKeyDown = (event: KeyboardEvent & { currentTarget: HTMLInputElement }) => {
|
||||
if (event.key === "Enter") {
|
||||
event.currentTarget.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "Escape") {
|
||||
skipBlurCommit = true;
|
||||
setIsEditing(false);
|
||||
setInternal(formatValue(currentValue()));
|
||||
setDisplayValue(currentValue());
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerEl}
|
||||
class={cn(
|
||||
"group/slider-input relative flex-1 min-w-0 h-7 bg-input rounded-md overflow-hidden border-none outline-none touch-none after:pointer-events-none after:absolute after:inset-0 after:rounded-md after:opacity-0 after:ring-1 after:ring-inset after:ring-ring after:z-20",
|
||||
props.class,
|
||||
)}
|
||||
classList={{
|
||||
"opacity-50": props.disabled,
|
||||
"after:opacity-100": isEditing(),
|
||||
"cursor-not-allowed": props.disabled,
|
||||
}}
|
||||
onPointerDown={handlePointerDown}
|
||||
>
|
||||
<div
|
||||
class="absolute left-0 top-0 bottom-0 bg-muted rounded-l-md pointer-events-none"
|
||||
classList={{
|
||||
"rounded-r-md": fillRatio() >= 1,
|
||||
"rounded-r-sm": fillRatio() < 1,
|
||||
}}
|
||||
style={{
|
||||
width: `calc(${TRACK_INSET_PX}px + ${fillRatio()} * (100% - ${TRACK_INSET_PX}px))`,
|
||||
}}
|
||||
>
|
||||
<Show when={!props.disabled}>
|
||||
<div class="absolute right-1 top-1/2 -translate-y-1/2 w-0.5 rounded-xs bg-input transition-all duration-75 opacity-0 h-3 group-hover/slider-input:opacity-70 group-focus-within/slider-input:opacity-70 group-data-[dragging=true]/slider-input:opacity-100 group-data-[dragging=true]/slider-input:h-4" />
|
||||
</Show>
|
||||
</div>
|
||||
<div class="relative z-0 flex items-center h-full justify-between pl-2">
|
||||
<div class="relative inline-block">
|
||||
<span class="invisible pointer-events-none whitespace-pre text-xxs text-foreground">
|
||||
{internal().length === 0 ? "0" : internal()}
|
||||
</span>
|
||||
<input
|
||||
ref={inputEl}
|
||||
class="absolute inset-0 w-full h-full cursor-default bg-transparent text-xxs text-foreground focus:outline-none disabled:cursor-not-allowed border-none outline-none p-0"
|
||||
type="text"
|
||||
value={internal()}
|
||||
onInput={(event) => setInternal(event.currentTarget.value)}
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
import { motion, useReducedMotion } from "motion/react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface ScrubberProps {
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
/** Number of decimal places to display */
|
||||
decimals?: number;
|
||||
/** Default value for uncontrolled usage */
|
||||
defaultValue?: number;
|
||||
/** Label displayed on the left side of the track */
|
||||
label?: string;
|
||||
/** Maximum value */
|
||||
max?: number;
|
||||
/** Minimum value */
|
||||
min?: number;
|
||||
/** Called when value changes during interaction */
|
||||
onValueChange?: (value: number) => void;
|
||||
/** Step increment */
|
||||
step?: number;
|
||||
/** Number of tick marks (0 to hide) */
|
||||
ticks?: number;
|
||||
/** Controlled value */
|
||||
value?: number;
|
||||
}
|
||||
|
||||
const clamp = (val: number, min: number, max: number) =>
|
||||
Math.min(Math.max(val, min), max);
|
||||
|
||||
const roundToStep = (val: number, step: number, min: number) =>
|
||||
Math.round((val - min) / step) * step + min;
|
||||
|
||||
export function Scrubber({
|
||||
label = "Value",
|
||||
value: controlledValue,
|
||||
defaultValue = 0,
|
||||
onValueChange,
|
||||
min = 0,
|
||||
max = 1,
|
||||
step = 0.01,
|
||||
decimals = 2,
|
||||
ticks = 9,
|
||||
className,
|
||||
}: ScrubberProps) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const trackRef = useRef<HTMLDivElement>(null);
|
||||
const [internalValue, setInternalValue] = useState(defaultValue);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const [isHoverDevice, setIsHoverDevice] = useState(false);
|
||||
|
||||
const isControlled = controlledValue !== undefined;
|
||||
const value = isControlled ? controlledValue : internalValue;
|
||||
const range = max - min;
|
||||
const percentage = range > 0 ? ((value - min) / range) * 100 : 0;
|
||||
const isActive = isDragging || (isHoverDevice && isHovering);
|
||||
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia("(hover: hover) and (pointer: fine)");
|
||||
setIsHoverDevice(mq.matches);
|
||||
const onChange = (e: MediaQueryListEvent) => setIsHoverDevice(e.matches);
|
||||
mq.addEventListener("change", onChange);
|
||||
return () => mq.removeEventListener("change", onChange);
|
||||
}, []);
|
||||
|
||||
const setValue = useCallback(
|
||||
(newValue: number) => {
|
||||
const clamped = clamp(roundToStep(newValue, step, min), min, max);
|
||||
if (!isControlled) {
|
||||
setInternalValue(clamped);
|
||||
}
|
||||
onValueChange?.(clamped);
|
||||
},
|
||||
[step, min, max, isControlled, onValueChange]
|
||||
);
|
||||
|
||||
const getValueFromPointer = useCallback(
|
||||
(clientX: number) => {
|
||||
const track = trackRef.current;
|
||||
if (!track) {
|
||||
return value;
|
||||
}
|
||||
const rect = track.getBoundingClientRect();
|
||||
const ratio = clamp((clientX - rect.left) / rect.width, 0, 1);
|
||||
return min + ratio * range;
|
||||
},
|
||||
[min, range, value]
|
||||
);
|
||||
|
||||
const handlePointerDown = useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
e.preventDefault();
|
||||
trackRef.current?.setPointerCapture(e.pointerId);
|
||||
setIsDragging(true);
|
||||
setValue(getValueFromPointer(e.clientX));
|
||||
},
|
||||
[getValueFromPointer, setValue]
|
||||
);
|
||||
|
||||
const handlePointerMove = useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!isDragging) {
|
||||
return;
|
||||
}
|
||||
setValue(getValueFromPointer(e.clientX));
|
||||
},
|
||||
[isDragging, getValueFromPointer, setValue]
|
||||
);
|
||||
|
||||
const handlePointerUp = useCallback(() => {
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
let next: number | undefined;
|
||||
switch (e.key) {
|
||||
case "ArrowRight":
|
||||
case "ArrowUp":
|
||||
next = value + step;
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
case "ArrowDown":
|
||||
next = value - step;
|
||||
break;
|
||||
case "Home":
|
||||
next = min;
|
||||
break;
|
||||
case "End":
|
||||
next = max;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
setValue(next);
|
||||
},
|
||||
[value, step, min, max, setValue]
|
||||
);
|
||||
|
||||
const springConfig = shouldReduceMotion
|
||||
? { duration: 0 }
|
||||
: { type: "spring" as const, duration: 0.25, bounce: 0.1 };
|
||||
|
||||
return (
|
||||
<div className={cn("relative w-full select-none", className)}>
|
||||
<div
|
||||
aria-label={label}
|
||||
aria-valuemax={max}
|
||||
aria-valuemin={min}
|
||||
aria-valuenow={Number(value.toFixed(decimals))}
|
||||
className="relative cursor-pointer overflow-hidden bg-accent/70 outline-offset-2 rounded-md h-[34px]"
|
||||
onKeyDown={handleKeyDown}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerUp={handlePointerUp}
|
||||
ref={trackRef}
|
||||
role="slider"
|
||||
style={{ touchAction: "none" }}
|
||||
tabIndex={0}
|
||||
>
|
||||
{/* Fill indicator */}
|
||||
<div
|
||||
className="pointer-events-none absolute inset-y-0 left-0 bg-foreground/10 rounded-md"
|
||||
style={{
|
||||
width: `${percentage}%`,
|
||||
transition: isDragging
|
||||
? "none"
|
||||
: "width 150ms cubic-bezier(0.23, 1, 0.32, 1)",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Tick marks */}
|
||||
{ticks > 0 && (
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
{Array.from({ length: ticks }, (_, i) => {
|
||||
const pos = ((i + 1) / (ticks + 1)) * 100;
|
||||
return (
|
||||
<div
|
||||
className="absolute top-1/2 bg-foreground/25"
|
||||
key={pos}
|
||||
style={{
|
||||
left: `${pos}%`,
|
||||
width: 1,
|
||||
height: 8,
|
||||
borderRadius: 999,
|
||||
transform: "translateX(-50%) translateY(-50%)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Scrub bar (capsule thumb) */}
|
||||
<div
|
||||
className="pointer-events-none absolute"
|
||||
style={{
|
||||
top: "50%",
|
||||
left: `${percentage}%`,
|
||||
transform: "translateX(-50%) translateY(-50%)",
|
||||
marginLeft: -6,
|
||||
zIndex: 3,
|
||||
transition: isDragging
|
||||
? "none"
|
||||
: "left 150ms cubic-bezier(0.23, 1, 0.32, 1)",
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
animate={{
|
||||
opacity: isActive ? 0.8 : 0.15,
|
||||
scaleX: isActive ? 1 : 0.7,
|
||||
scaleY: isActive ? 1 : 0.7,
|
||||
}}
|
||||
className="bg-foreground/90"
|
||||
style={{
|
||||
width: 4,
|
||||
height: 20,
|
||||
borderRadius: 999,
|
||||
}}
|
||||
transition={springConfig}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<div className="pointer-events-none absolute text-xs z-10 top-1/2 left-[12px] -translate-y-1/2 whitespace-nowrap text-muted-foreground">
|
||||
{label}
|
||||
</div>
|
||||
|
||||
{/* Value display */}
|
||||
<div
|
||||
className="pointer-events-none absolute top-1/2 z-10 font-medium text-xs right-[14px] -translate-y-1/2 text-foreground font-mono"
|
||||
style={{ fontVariantNumeric: "tabular-nums" }}
|
||||
>
|
||||
{value.toFixed(decimals)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Scrubber;
|
||||
@@ -1,57 +1,118 @@
|
||||
import * as React from "react";
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import {
|
||||
type SliderFillProps,
|
||||
Slider as SliderPrimitive,
|
||||
type SliderRootProps,
|
||||
type SliderThumbProps,
|
||||
type SliderTrackProps,
|
||||
useSliderContext,
|
||||
} from "@kobalte/core/slider";
|
||||
import {
|
||||
type ComponentProps,
|
||||
createMemo,
|
||||
For,
|
||||
mergeProps,
|
||||
splitProps,
|
||||
untrack,
|
||||
type ValidComponent,
|
||||
} from "solid-js";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Slider({
|
||||
className,
|
||||
defaultValue,
|
||||
value,
|
||||
min = 0,
|
||||
max = 100,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
||||
const _values = React.useMemo(
|
||||
() =>
|
||||
Array.isArray(value)
|
||||
? value
|
||||
: Array.isArray(defaultValue)
|
||||
? defaultValue
|
||||
: [min, max],
|
||||
[value, defaultValue, min, max]
|
||||
);
|
||||
type SliderProps<T extends ValidComponent = "div"> = PolymorphicProps<T, SliderRootProps<T>> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const Slider = <T extends ValidComponent = "div">(rawProps: SliderProps<T>) => {
|
||||
const props = mergeProps({ minValue: 0, maxValue: 100 } as SliderProps<T>, rawProps);
|
||||
const [local, others] = splitProps(props as SliderProps, ["class", "defaultValue", "value"]);
|
||||
|
||||
const values = createMemo(() => {
|
||||
if (Array.isArray(untrack(() => local.value))) return untrack(() => local.value);
|
||||
if (Array.isArray(local.defaultValue)) return local.defaultValue;
|
||||
return [others.minValue, others.maxValue];
|
||||
});
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
<SliderPrimitive
|
||||
data-slot="slider"
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50",
|
||||
className
|
||||
defaultValue={local.defaultValue}
|
||||
value={local.value}
|
||||
class={cn(
|
||||
"relative z-slider flex w-full touch-none select-none items-center data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col data-disabled:opacity-50",
|
||||
local.class,
|
||||
)}
|
||||
{...props}
|
||||
{...others}
|
||||
>
|
||||
<SliderPrimitive.Track
|
||||
data-slot="slider-track"
|
||||
className="bg-white/15 relative grow overflow-hidden rounded-full h-1 w-full"
|
||||
>
|
||||
<SliderPrimitive.Range
|
||||
data-slot="slider-range"
|
||||
className="bg-primary absolute h-full"
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
{Array.from({ length: _values.length }, (_, index) => (
|
||||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
key={index}
|
||||
className="border-primary bg-foreground ring-ring/50 block size-3 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-none disabled:pointer-events-none"
|
||||
/>
|
||||
))}
|
||||
</SliderPrimitive.Root>
|
||||
<SliderTrack>
|
||||
<SliderFill />
|
||||
</SliderTrack>
|
||||
<For each={values()}>{() => <SliderThumb />}</For>
|
||||
</SliderPrimitive>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type SliderTrackComponentProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
SliderTrackProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class" | "children">;
|
||||
|
||||
const SliderTrack = <T extends ValidComponent = "div">(props: SliderTrackComponentProps<T>) => {
|
||||
const [local, others] = splitProps(props as SliderTrackComponentProps, ["class", "children"]);
|
||||
const context = useSliderContext();
|
||||
return (
|
||||
<SliderPrimitive.Track
|
||||
data-slot="slider-track"
|
||||
data-orientation={context.state.orientation()}
|
||||
class={cn("relative z-slider-track grow select-none overflow-hidden", local.class)}
|
||||
{...others}
|
||||
>
|
||||
{local.children}
|
||||
</SliderPrimitive.Track>
|
||||
);
|
||||
};
|
||||
|
||||
type SliderFillComponentProps<T extends ValidComponent = "div"> = PolymorphicProps<
|
||||
T,
|
||||
SliderFillProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const SliderFill = <T extends ValidComponent = "div">(props: SliderFillComponentProps<T>) => {
|
||||
const [local, others] = splitProps(props as SliderFillComponentProps, ["class"]);
|
||||
const context = useSliderContext();
|
||||
return (
|
||||
<SliderPrimitive.Fill
|
||||
data-slot="slider-range"
|
||||
data-orientation={context.state.orientation()}
|
||||
class={cn(
|
||||
"absolute z-slider-range data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SliderThumbComponentProps<T extends ValidComponent = "span"> = PolymorphicProps<
|
||||
T,
|
||||
SliderThumbProps<T>
|
||||
> &
|
||||
Pick<ComponentProps<T>, "class">;
|
||||
|
||||
const SliderThumb = <T extends ValidComponent = "span">(props: SliderThumbComponentProps<T>) => {
|
||||
const [local, others] = splitProps(props as SliderThumbComponentProps, ["class"]);
|
||||
return (
|
||||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
class={cn(
|
||||
"z-slider-thumb block shrink-0 select-none disabled:pointer-events-none disabled:opacity-50",
|
||||
local.class,
|
||||
)}
|
||||
{...others}
|
||||
>
|
||||
<SliderPrimitive.Input />
|
||||
</SliderPrimitive.Thumb>
|
||||
);
|
||||
};
|
||||
|
||||
export { Slider };
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
import { Accessor, createContext, createEffect, createResource, createMemo, createSignal, onCleanup, onMount, Resource, untrack, useContext, type JSX } from 'solid-js';
|
||||
import { useSearchParams } from "@solidjs/router";
|
||||
import { ControlMeta, AnimationSlot } from '@/types';
|
||||
import CanvasKitInit from "canvaskit-wasm/full";
|
||||
import type { CanvasKit, Surface, ManagedSkottieAnimation, Font, Paint, Typeface } from "canvaskit-wasm/full";
|
||||
|
||||
const MIN_ZOOM = 0.1;
|
||||
const MAX_ZOOM = 32;
|
||||
const ZOOM_SENSITIVITY = 0.003;
|
||||
const INITIAL_MARGIN_Y = 100; // CSS px above and below the animation
|
||||
const LABEL_FONT_SIZE = 11; // CSS px, screen space (does not scale with zoom)
|
||||
const LABEL_GAP_Y = 7; // CSS px between the label baseline and the frame
|
||||
const LABEL_MIN_WIDTH = 24; // CSS px the label may occupy on a tiny frame
|
||||
const LOTTIE_URL = "/lottie.json";
|
||||
const CONTROLS_URL = "/controls.json";
|
||||
const LABEL_FONT_URL = new URL('@/assets/fonts/Inter-VariableFont_opsz,wght.ttf', import.meta.url).toString();
|
||||
|
||||
const CanvasContext = createContext<{
|
||||
zoom: Accessor<number>;
|
||||
playing: Accessor<boolean>;
|
||||
currentFrame: Accessor<number>;
|
||||
totalFrames: Accessor<number>;
|
||||
fps: Accessor<number>;
|
||||
slots: Accessor<AnimationSlot[]>;
|
||||
controls: Resource<Record<string, ControlMeta>>;
|
||||
setScalarSlot(id: string, value: number): void;
|
||||
setColorSlot(id: string, rgba: [number, number, number, number]): void;
|
||||
setVec2Slot(id: string, xy: [number, number]): void;
|
||||
setTextSlot(id: string, text: string): void;
|
||||
togglePlayback(): void;
|
||||
seek(frame: number): void;
|
||||
zoomByCentered(factor: number): void;
|
||||
resetCamera(): void;
|
||||
}>();
|
||||
|
||||
export function CanvasProvider(props: { children: JSX.Element }) {
|
||||
let surface: Surface | null = null;
|
||||
let canvas!: HTMLCanvasElement;
|
||||
let labelFont: Font | null = null;
|
||||
let labelPaint: Paint | null = null;
|
||||
let rafId: number | null = null;
|
||||
let dragging = false;
|
||||
let lastTs = 0;
|
||||
let dirty = true;
|
||||
|
||||
const observer = new ResizeObserver(() => resize());
|
||||
const [searchParams] = useSearchParams();
|
||||
const [playing, setPlaying] = createSignal(false);
|
||||
const [currentFrame, setCurrentFrame] = createSignal(0);
|
||||
const [canvasKit] = createResource(loadCanvasKit);
|
||||
const [lottie] = createResource(loadLottie);
|
||||
const [controls] = createResource(loadControlsMeta);
|
||||
const [labelTypeface] = createResource(canvasKit, loadLabelTypeface);
|
||||
const [camera, setCamera] = createSignal({
|
||||
x: 0,
|
||||
y: 0,
|
||||
zoom: 1,
|
||||
});
|
||||
const zoom = () => camera().zoom;
|
||||
const animation = createMemo(() => {
|
||||
const ck = canvasKit();
|
||||
const file = lottie();
|
||||
if (!ck || !file) return null;
|
||||
return ck.MakeManagedAnimation(file);
|
||||
});
|
||||
const animationName = createMemo(() => {
|
||||
try {
|
||||
const nm = (JSON.parse(lottie() ?? "") as { nm?: unknown }).nm;
|
||||
if (typeof nm === "string" && nm.trim()) return nm.trim();
|
||||
} catch { /** ignore */ }
|
||||
return "Animation 1";
|
||||
});
|
||||
|
||||
const fps = createMemo(() => {
|
||||
const anim = animation();
|
||||
if (!anim) return 0;
|
||||
return anim.fps() || 60;
|
||||
});
|
||||
|
||||
const totalFrames = createMemo(() => {
|
||||
const anim = animation();
|
||||
if (!anim) return 0;
|
||||
return Math.max(1, Math.round(anim.duration() * fps()));
|
||||
});
|
||||
|
||||
const slots = createMemo(() => {
|
||||
const anim = animation();
|
||||
if (!anim) return [];
|
||||
const info = anim.getSlotInfo();
|
||||
const slots: AnimationSlot[] = [];
|
||||
for (const id of info.scalarSlotIDs) {
|
||||
slots.push({ id, type: "scalar", value: anim.getScalarSlot(id) ?? 0 });
|
||||
}
|
||||
for (const id of info.colorSlotIDs) {
|
||||
const c = anim.getColorSlot(id);
|
||||
slots.push({
|
||||
id,
|
||||
type: "color",
|
||||
value: c ? [c[0], c[1], c[2], c[3]] : [0, 0, 0, 1],
|
||||
});
|
||||
}
|
||||
for (const id of info.vec2SlotIDs) {
|
||||
const v = anim.getVec2Slot(id);
|
||||
slots.push({ id, type: "vec2", value: v ? [v[0], v[1]] : [0, 0] });
|
||||
}
|
||||
for (const id of info.textSlotIDs) {
|
||||
slots.push({ id, type: "text", value: anim.getTextSlot(id)?.text ?? "" });
|
||||
}
|
||||
return slots;
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (canvasKit()) resize();
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const ck = canvasKit();
|
||||
const typeface = labelTypeface();
|
||||
if (!ck || !typeface || labelPaint) return;
|
||||
labelPaint = new ck.Paint();
|
||||
labelPaint.setColor(ck.Color4f(0.64, 0.64, 0.64, 1)); // matches muted-foreground
|
||||
labelPaint.setAntiAlias(true);
|
||||
labelFont = new ck.Font(typeface, LABEL_FONT_SIZE);
|
||||
dirty = true;
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (animation()) resetCamera();
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const raw = searchParams.frame;
|
||||
untrack(() => {
|
||||
const frame = raw != null ? Number(raw) : null;
|
||||
if (frame != null && Number.isFinite(frame)) {
|
||||
pause();
|
||||
seek(frame);
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const setScalarSlot = (id: string, value: number) => {
|
||||
animation()?.setScalarSlot(id, value);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
const setColorSlot = (id: string, rgba: [number, number, number, number]) => {
|
||||
const ck = canvasKit();
|
||||
if (!ck) return;
|
||||
animation()?.setColorSlot(id, ck.Color4f(rgba[0], rgba[1], rgba[2], rgba[3]));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
const setVec2Slot = (id: string, xy: [number, number]) => {
|
||||
animation()?.setVec2Slot(id, xy);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
const setTextSlot = (id: string, text: string) => {
|
||||
const anim = animation();
|
||||
const ck = canvasKit();
|
||||
if (!anim || !ck) return;
|
||||
const current = anim.getTextSlot(id);
|
||||
if (!current) return;
|
||||
current.text = text;
|
||||
anim.setTextSlot(id, new ck.SlottableTextProperty(current));
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
const play = () => {
|
||||
if (playing()) return;
|
||||
setPlaying(true);
|
||||
lastTs = 0; // reset so the first tick after resume has no jump
|
||||
};
|
||||
|
||||
const pause = () => {
|
||||
if (!playing()) return;
|
||||
setPlaying(false);
|
||||
};
|
||||
|
||||
const togglePlayback = () => {
|
||||
if (playing()) pause();
|
||||
else play();
|
||||
};
|
||||
|
||||
const seek = (frame: number) => {
|
||||
setCurrentFrame(Math.max(0, Math.min(frame, totalFrames())))
|
||||
dirty = true;
|
||||
};
|
||||
|
||||
const resize = () => {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const width = Math.max(1, Math.floor(canvas.clientWidth * dpr));
|
||||
const height = Math.max(1, Math.floor(canvas.clientHeight * dpr));
|
||||
|
||||
if (canvas.width === width && canvas.height === height && surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ck = canvasKit();
|
||||
|
||||
if (!ck) return;
|
||||
|
||||
surface?.delete();
|
||||
surface = ck.MakeWebGLCanvasSurface(canvas);
|
||||
if (!surface) {
|
||||
throw new Error("Could not create a WebGL surface for CanvasKit.");
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const cx = (e.clientX - rect.left) * dpr;
|
||||
const cy = (e.clientY - rect.top) * dpr;
|
||||
const factor = Math.exp(-e.deltaY * ZOOM_SENSITIVITY);
|
||||
const next = clamp(zoom() * factor, MIN_ZOOM, MAX_ZOOM);
|
||||
const applied = next / zoom();
|
||||
setCamera(cam => ({
|
||||
...cam,
|
||||
x: cx - (cx - cam.x) * applied,
|
||||
y: cy - (cy - cam.y) * applied,
|
||||
zoom: next,
|
||||
}));
|
||||
} else {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
setCamera(cam => ({
|
||||
...cam,
|
||||
x: cam.x - e.deltaX * dpr,
|
||||
y: cam.y - e.deltaY * dpr,
|
||||
}));
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
};
|
||||
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
if (e.button !== 0 && e.button !== 1) return; // left or middle drag
|
||||
dragging = true;
|
||||
canvas.style.cursor = "grabbing";
|
||||
dirty = true;
|
||||
};
|
||||
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
if (!dragging) return;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
setCamera(cam => ({
|
||||
...cam,
|
||||
x: cam.x + e.movementX * dpr,
|
||||
y: cam.y + e.movementY * dpr,
|
||||
}));
|
||||
dirty = true;
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
canvas.style.cursor = "grab";
|
||||
dirty = true;
|
||||
};
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.code !== "Space" && e.key !== " ") return;
|
||||
const target = e.target as HTMLElement | null;
|
||||
// don't hijack space while typing in an input, textarea, or contenteditable
|
||||
if (target && (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName))) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
togglePlayback();
|
||||
};
|
||||
|
||||
const resetCamera = () => {
|
||||
const anim = animation();
|
||||
if (!anim) return;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const [w, h] = anim.size();
|
||||
const cw = canvas.width;
|
||||
const ch = canvas.height;
|
||||
if (!cw || !ch || !w || !h) return;
|
||||
const dh = h * Math.min(cw / w, ch / h);
|
||||
const zoom = clamp((ch - 2 * INITIAL_MARGIN_Y * dpr) / dh, MIN_ZOOM, MAX_ZOOM);
|
||||
setCamera({
|
||||
x: (cw / 2) * (1 - zoom),
|
||||
y: (ch / 2) * (1 - zoom),
|
||||
zoom,
|
||||
});
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
const tick = (ts: number) => {
|
||||
const ck = canvasKit();
|
||||
const ctx = surface?.getCanvas();
|
||||
const anim = animation();
|
||||
|
||||
if (playing() && totalFrames() > 0) {
|
||||
if (!lastTs) lastTs = ts; // first tick after play(): no elapsed time yet
|
||||
const dt = (ts - lastTs) / 1000;
|
||||
setCurrentFrame((currentFrame() + dt * fps()) % totalFrames());
|
||||
lastTs = ts;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty && ctx && ck && anim) {
|
||||
ctx.clear(ck.TRANSPARENT);
|
||||
|
||||
const { x, y, zoom } = camera();
|
||||
const [w, h] = anim.size();
|
||||
const cw = canvas.width;
|
||||
const ch = canvas.height;
|
||||
const scale = Math.min(cw / w, ch / h);
|
||||
const dw = w * scale;
|
||||
const dh = h * scale;
|
||||
const left = (cw - dw) / 2;
|
||||
const top = (ch - dh) / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.scale(zoom, zoom);
|
||||
anim.seekFrame(currentFrame());
|
||||
anim.render(ctx, ck.LTRBRect(left, top, left + dw, top + dh));
|
||||
ctx.restore();
|
||||
|
||||
if (labelFont && labelPaint) {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
labelFont.setSize(LABEL_FONT_SIZE * dpr);
|
||||
const maxWidth = Math.max(zoom * dw, LABEL_MIN_WIDTH * dpr);
|
||||
const text = fitLabel(labelFont, animationName(), maxWidth);
|
||||
if (text) {
|
||||
ctx.drawText(text, x + zoom * left, y + zoom * top - LABEL_GAP_Y * dpr, labelPaint, labelFont);
|
||||
}
|
||||
}
|
||||
|
||||
surface?.flush();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
rafId = requestAnimationFrame(tick);
|
||||
};
|
||||
|
||||
const zoomByCentered = (factor: number) => {
|
||||
const cx = canvas.width / 2;
|
||||
const cy = canvas.height / 2;
|
||||
const next = clamp(zoom() * factor, MIN_ZOOM, MAX_ZOOM);
|
||||
const applied = next / zoom();
|
||||
setCamera(cam => ({
|
||||
...cam,
|
||||
x: cx - (cx - cam.x) * applied,
|
||||
y: cy - (cy - cam.y) * applied,
|
||||
zoom: next,
|
||||
}));
|
||||
dirty = true;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
observer.observe(canvas);
|
||||
canvas.style.cursor = "grab";
|
||||
canvas.style.touchAction = "none";
|
||||
canvas.addEventListener("wheel", onWheel, { passive: false });
|
||||
canvas.addEventListener("pointerdown", onPointerDown);
|
||||
canvas.addEventListener("dblclick", resetCamera);
|
||||
window.addEventListener("pointermove", onPointerMove);
|
||||
window.addEventListener("pointerup", onPointerUp);
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
|
||||
if (!rafId) rafId = requestAnimationFrame(tick);
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
surface?.delete();
|
||||
surface = null;
|
||||
labelFont?.delete();
|
||||
labelFont = null;
|
||||
labelPaint?.delete();
|
||||
labelPaint = null;
|
||||
labelTypeface()?.delete();
|
||||
animation()?.delete();
|
||||
|
||||
canvas.removeEventListener("wheel", onWheel);
|
||||
canvas.removeEventListener("pointerdown", onPointerDown);
|
||||
canvas.removeEventListener("dblclick", resetCamera);
|
||||
window.removeEventListener("pointermove", onPointerMove);
|
||||
window.removeEventListener("pointerup", onPointerUp);
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
|
||||
if (rafId) cancelAnimationFrame(rafId);
|
||||
});
|
||||
|
||||
return (
|
||||
<CanvasContext.Provider
|
||||
value={{
|
||||
zoom,
|
||||
playing,
|
||||
currentFrame,
|
||||
totalFrames,
|
||||
fps,
|
||||
slots,
|
||||
controls,
|
||||
setScalarSlot,
|
||||
setColorSlot,
|
||||
setVec2Slot,
|
||||
setTextSlot,
|
||||
togglePlayback,
|
||||
seek,
|
||||
zoomByCentered,
|
||||
resetCamera,
|
||||
}}>
|
||||
<canvas ref={canvas} id="main-canvas" class="block h-full w-full" />
|
||||
{props.children}
|
||||
</CanvasContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useCanvas() {
|
||||
const context = useContext(CanvasContext);
|
||||
if (!context) {
|
||||
throw new Error('useCanvas must be used within a CanvasProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function loadCanvasKit(): Promise<CanvasKit> {
|
||||
return CanvasKitInit({ locateFile: () => "/canvaskit.wasm" })
|
||||
}
|
||||
|
||||
async function loadLottie() {
|
||||
const res = await fetch(LOTTIE_URL);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load ${LOTTIE_URL} (HTTP ${res.status})`);
|
||||
}
|
||||
|
||||
return await res.text();
|
||||
}
|
||||
|
||||
async function loadLabelTypeface(ck: CanvasKit): Promise<Typeface | null> {
|
||||
try {
|
||||
const res = await fetch(LABEL_FONT_URL);
|
||||
if (!res.ok) return null;
|
||||
return ck.Typeface.MakeFreeTypeFaceFromData(await res.arrayBuffer());
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadControlsMeta(): Promise<Record<string, ControlMeta>> {
|
||||
try {
|
||||
const res = await fetch(CONTROLS_URL);
|
||||
if (!res.ok) return {};
|
||||
const data = (await res.json()) as { controls?: ControlMeta[] };
|
||||
return Object.fromEntries((data.controls ?? []).map((c) => [c.sid, c]));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
function textWidth(font: Font, text: string): number {
|
||||
let width = 0;
|
||||
for (const advance of font.getGlyphWidths(font.getGlyphIDs(text))) {
|
||||
width += advance;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
// Truncate the label with an ellipsis once it is wider than the frame, like Figma
|
||||
function fitLabel(font: Font, text: string, maxWidth: number): string {
|
||||
if (textWidth(font, text) <= maxWidth) return text;
|
||||
const ellipsis = "…";
|
||||
const budget = maxWidth - textWidth(font, ellipsis);
|
||||
let width = 0;
|
||||
let out = "";
|
||||
for (const char of text) {
|
||||
width += textWidth(font, char);
|
||||
if (width > budget) break;
|
||||
out += char;
|
||||
}
|
||||
return out && out + ellipsis;
|
||||
}
|
||||
@@ -1,10 +1,60 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@fontsource-variable/inter";
|
||||
@import "./styles/base.css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--text-xxs: 11px;
|
||||
--font-heading: var(--font-sans);
|
||||
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
||||
--font-weight-strong: 450;
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-background: var(--background);
|
||||
--color-canvas: var(--canvas);
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 10px;
|
||||
--radius-2xl: 12px;
|
||||
--radius-3xl: 14px;
|
||||
--radius-4xl: 16px;
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 10px;
|
||||
--canvas: hsla(0, 0%, 9%, 1);
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
@@ -23,68 +73,113 @@
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--radius: 6px;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--canvas: hsla(0, 0%, 9%, 1);
|
||||
--background: hsla(0, 0%, 11%, 1);
|
||||
--foreground: hsla(0, 0%, 100%, 1);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.269 0 0);
|
||||
--popover: hsla(0, 0%, 9%, 1);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary: hsla(207, 100%, 50%, 1);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary: hsla(0, 0%, 16%, 1);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.371 0 0);
|
||||
--muted: hsla(0, 0%, 25%, 1);
|
||||
--muted-foreground: hsla(0, 0%, 62%, 1);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--border: hsla(0, 0%, 15%, 1);
|
||||
--input: hsla(0, 0%, 16%, 1);
|
||||
--ring: hsla(207, 100%, 50%, 1);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
html {
|
||||
@apply font-sans;
|
||||
}
|
||||
|
||||
.z-font-heading {
|
||||
@apply font-heading;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
border-color: var(--border);
|
||||
@utility no-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
@layer utilities {
|
||||
|
||||
.focus-ring:focus-visible,
|
||||
.focus-ring:has(:focus),
|
||||
.focus-ring[data-dragging='true'] {
|
||||
border-color: var(--ring);
|
||||
box-shadow: inset 0 0 0 1px var(--ring);
|
||||
outline: none;
|
||||
transition:
|
||||
box-shadow 0.2s,
|
||||
outline 0.2s,
|
||||
border 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('./assets/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
src: url('./assets/fonts/JetBrainsMono-VariableFont.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-stretch: normal;
|
||||
font-style: normal;
|
||||
font-feature-settings: 'zero' 1;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* @refresh reload */
|
||||
import './index.css';
|
||||
import { render } from 'solid-js/web';
|
||||
import 'solid-devtools';
|
||||
|
||||
import { Root } from './router';
|
||||
|
||||
const root = document.getElementById('root');
|
||||
|
||||
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||
throw new Error(
|
||||
'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?',
|
||||
);
|
||||
}
|
||||
|
||||
render(() => <Root />, root!);
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { Canvas } from "canvaskit-wasm/full";
|
||||
|
||||
export const MIN_ZOOM = 0.1;
|
||||
export const MAX_ZOOM = 32;
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* A 2D pan/zoom camera operating in device-pixel space. The mapping from a
|
||||
* scene point to a screen point is `screen = scene * zoom + (x, y)`, i.e. a
|
||||
* uniform scale followed by a translation — the same model Figma uses.
|
||||
*/
|
||||
export class Camera {
|
||||
x = 0;
|
||||
y = 0;
|
||||
zoom = 1;
|
||||
|
||||
reset(): void {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.zoom = 1;
|
||||
}
|
||||
|
||||
isIdentity(): boolean {
|
||||
return this.x === 0 && this.y === 0 && this.zoom === 1;
|
||||
}
|
||||
|
||||
panBy(dx: number, dy: number): void {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies the zoom by `factor` while keeping the screen point (cx, cy)
|
||||
* anchored — so the content under the cursor stays put as you zoom.
|
||||
*/
|
||||
zoomAt(factor: number, cx: number, cy: number): void {
|
||||
const next = clamp(this.zoom * factor, MIN_ZOOM, MAX_ZOOM);
|
||||
const applied = next / this.zoom;
|
||||
this.x = cx - (cx - this.x) * applied;
|
||||
this.y = cy - (cy - this.y) * applied;
|
||||
this.zoom = next;
|
||||
}
|
||||
|
||||
/** Pushes the camera transform onto a Skia canvas (call between save/restore). */
|
||||
apply(canvas: Canvas): void {
|
||||
canvas.translate(this.x, this.y);
|
||||
canvas.scale(this.zoom, this.zoom);
|
||||
}
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
import CanvasKitInit, {
|
||||
type CanvasKit,
|
||||
type Surface,
|
||||
type ManagedSkottieAnimation,
|
||||
} from "canvaskit-wasm/full";
|
||||
|
||||
import { Camera } from "@/lib/camera";
|
||||
|
||||
let canvasKitPromise: Promise<CanvasKit> | null = null;
|
||||
|
||||
/** Loads (and caches) the CanvasKit WASM module. */
|
||||
export function loadCanvasKit(): Promise<CanvasKit> {
|
||||
if (!canvasKitPromise) {
|
||||
canvasKitPromise = CanvasKitInit({
|
||||
// The wasm binary is copied into /public by scripts/copy-canvaskit.mjs.
|
||||
locateFile: () => "/canvaskit.wasm",
|
||||
});
|
||||
}
|
||||
return canvasKitPromise;
|
||||
}
|
||||
|
||||
// How aggressively a wheel/pinch gesture changes zoom.
|
||||
const ZOOM_SENSITIVITY = 0.003;
|
||||
|
||||
/** A property the animation has marked as slottable, with its current value. */
|
||||
export type AnimationSlot =
|
||||
| { id: string; type: "scalar"; value: number }
|
||||
| { id: string; type: "color"; value: [number, number, number, number] }
|
||||
| { id: string; type: "vec2"; value: [number, number] }
|
||||
| { id: string; type: "text"; value: string };
|
||||
|
||||
export interface LottiePlayerCallbacks {
|
||||
/** Fired every rendered frame with the playhead frame and total frame count. */
|
||||
onFrame?: (currentFrame: number, totalFrames: number) => void;
|
||||
/** Fired whenever the play/pause state changes. */
|
||||
onPlayStateChange?: (playing: boolean) => void;
|
||||
/** Fired whenever the camera (pan/zoom) changes, with the current zoom factor. */
|
||||
onCameraChange?: (zoom: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a Lottie animation onto a <canvas> using Skia's Skottie module via
|
||||
* CanvasKit. Owns its own requestAnimationFrame loop and a WebGL surface that
|
||||
* is recreated on resize. The playhead is tracked in frames; playback advances
|
||||
* it off wall-clock time scaled by the animation's fps, so it plays at native
|
||||
* speed regardless of the render frame rate.
|
||||
*
|
||||
* A {@link Camera} provides Figma-style pan/zoom over the whole scene: scroll
|
||||
* to pan, ⌘/ctrl+scroll or pinch to zoom (anchored on the cursor), drag to pan,
|
||||
* and double-click to reset.
|
||||
*/
|
||||
export class LottiePlayer {
|
||||
private surface: Surface | null = null;
|
||||
private rafId = 0;
|
||||
private playing = false;
|
||||
private currentFrame = 0;
|
||||
private lastTs = 0;
|
||||
private dirty = true;
|
||||
private readonly fps: number;
|
||||
private readonly totalFrames: number;
|
||||
|
||||
private readonly camera = new Camera();
|
||||
private dragging = false;
|
||||
|
||||
constructor(
|
||||
private readonly ck: CanvasKit,
|
||||
private readonly canvas: HTMLCanvasElement,
|
||||
private readonly animation: ManagedSkottieAnimation,
|
||||
private readonly callbacks: LottiePlayerCallbacks = {}
|
||||
) {
|
||||
this.fps = animation.fps() || 60;
|
||||
this.totalFrames = Math.max(1, Math.round(animation.duration() * this.fps));
|
||||
this.resize();
|
||||
this.attachInput();
|
||||
this.rafId = requestAnimationFrame(this.tick);
|
||||
}
|
||||
|
||||
/** Builds a player from a Lottie JSON string, loading CanvasKit if needed. */
|
||||
static async create(
|
||||
canvas: HTMLCanvasElement,
|
||||
lottieJson: string,
|
||||
callbacks?: LottiePlayerCallbacks
|
||||
): Promise<LottiePlayer> {
|
||||
const ck = await loadCanvasKit();
|
||||
const animation = ck.MakeManagedAnimation(lottieJson);
|
||||
if (!animation) {
|
||||
throw new Error("CanvasKit could not parse the Lottie file.");
|
||||
}
|
||||
return new LottiePlayer(ck, canvas, animation, callbacks);
|
||||
}
|
||||
|
||||
getFps(): number {
|
||||
return this.fps;
|
||||
}
|
||||
|
||||
getTotalFrames(): number {
|
||||
return this.totalFrames;
|
||||
}
|
||||
|
||||
/** The current playhead position, in frames (may be fractional while playing). */
|
||||
getCurrentFrame(): number {
|
||||
return this.currentFrame;
|
||||
}
|
||||
|
||||
getZoom(): number {
|
||||
return this.camera.zoom;
|
||||
}
|
||||
|
||||
// --- Slots (live property overrides) ------------------------------------
|
||||
//
|
||||
// Skottie exposes properties the animation author marked as "slottable" (see
|
||||
// the `slots`/`sid` convention in the write-lottie skill). They can be read
|
||||
// and overwritten live without re-parsing — overriding one just shows on the
|
||||
// next rendered frame. `getSlotInfo()` reports the IDs grouped by type.
|
||||
|
||||
/** Lists every slottable property with its current value. */
|
||||
getSlots(): AnimationSlot[] {
|
||||
const info = this.animation.getSlotInfo();
|
||||
const slots: AnimationSlot[] = [];
|
||||
for (const id of info.scalarSlotIDs) {
|
||||
slots.push({ id, type: "scalar", value: this.animation.getScalarSlot(id) ?? 0 });
|
||||
}
|
||||
for (const id of info.colorSlotIDs) {
|
||||
const c = this.animation.getColorSlot(id);
|
||||
slots.push({
|
||||
id,
|
||||
type: "color",
|
||||
value: c ? [c[0], c[1], c[2], c[3]] : [0, 0, 0, 1],
|
||||
});
|
||||
}
|
||||
for (const id of info.vec2SlotIDs) {
|
||||
const v = this.animation.getVec2Slot(id);
|
||||
slots.push({ id, type: "vec2", value: v ? [v[0], v[1]] : [0, 0] });
|
||||
}
|
||||
for (const id of info.textSlotIDs) {
|
||||
slots.push({ id, type: "text", value: this.animation.getTextSlot(id)?.text ?? "" });
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
/** Overrides a scalar slot. Color/vec2 components are 0..1 / animation units. */
|
||||
setScalarSlot(id: string, value: number): void {
|
||||
this.animation.setScalarSlot(id, value);
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
/** Overrides a color slot. Components are 0..1, RGBA. */
|
||||
setColorSlot(id: string, rgba: [number, number, number, number]): void {
|
||||
this.animation.setColorSlot(id, this.ck.Color4f(rgba[0], rgba[1], rgba[2], rgba[3]));
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
/** Overrides a 2D vector slot (e.g. a position), in animation coordinates. */
|
||||
setVec2Slot(id: string, xy: [number, number]): void {
|
||||
this.animation.setVec2Slot(id, xy);
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
/** Overrides a text slot's string, preserving the slot's existing styling. */
|
||||
setTextSlot(id: string, text: string): void {
|
||||
const current = this.animation.getTextSlot(id);
|
||||
if (!current) return;
|
||||
current.text = text;
|
||||
// The constructor fills out any fields the bindings require as defaults.
|
||||
this.animation.setTextSlot(id, new this.ck.SlottableTextProperty(current));
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
isPlaying(): boolean {
|
||||
return this.playing;
|
||||
}
|
||||
|
||||
play(): void {
|
||||
if (this.playing) return;
|
||||
this.playing = true;
|
||||
this.lastTs = 0; // reset so the first tick after resume has no jump
|
||||
this.callbacks.onPlayStateChange?.(true);
|
||||
}
|
||||
|
||||
pause(): void {
|
||||
if (!this.playing) return;
|
||||
this.playing = false;
|
||||
this.callbacks.onPlayStateChange?.(false);
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
this.playing ? this.pause() : this.play();
|
||||
}
|
||||
|
||||
/** Seeks to an absolute frame. */
|
||||
seek(frame: number): void {
|
||||
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
|
||||
this.dirty = true;
|
||||
this.callbacks.onFrame?.(this.currentFrame, this.totalFrames);
|
||||
}
|
||||
|
||||
/** Zooms by `factor` around the canvas center (for on-screen +/- controls). */
|
||||
zoomByCentered(factor: number): void {
|
||||
this.camera.zoomAt(factor, this.canvas.width / 2, this.canvas.height / 2);
|
||||
this.onCameraChanged();
|
||||
}
|
||||
|
||||
/** Resets pan/zoom back to the fitted view. */
|
||||
resetCamera(): void {
|
||||
this.camera.reset();
|
||||
this.onCameraChanged();
|
||||
}
|
||||
|
||||
/** Syncs the backing store to the element's CSS size and recreates the surface. */
|
||||
resize(): void {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const width = Math.max(1, Math.floor(this.canvas.clientWidth * dpr));
|
||||
const height = Math.max(1, Math.floor(this.canvas.clientHeight * dpr));
|
||||
if (this.canvas.width === width && this.canvas.height === height && this.surface) {
|
||||
return;
|
||||
}
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
|
||||
this.surface?.delete();
|
||||
const surface = this.ck.MakeWebGLCanvasSurface(this.canvas);
|
||||
if (!surface) {
|
||||
throw new Error("Could not create a WebGL surface for CanvasKit.");
|
||||
}
|
||||
this.surface = surface;
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.detachInput();
|
||||
this.surface?.delete();
|
||||
this.surface = null;
|
||||
this.animation.delete();
|
||||
}
|
||||
|
||||
// --- Camera input -------------------------------------------------------
|
||||
|
||||
private attachInput(): void {
|
||||
this.canvas.style.cursor = "grab";
|
||||
this.canvas.style.touchAction = "none";
|
||||
this.canvas.addEventListener("wheel", this.onWheel, { passive: false });
|
||||
this.canvas.addEventListener("pointerdown", this.onPointerDown);
|
||||
this.canvas.addEventListener("dblclick", this.onDoubleClick);
|
||||
window.addEventListener("pointermove", this.onPointerMove);
|
||||
window.addEventListener("pointerup", this.onPointerUp);
|
||||
}
|
||||
|
||||
private detachInput(): void {
|
||||
this.canvas.removeEventListener("wheel", this.onWheel);
|
||||
this.canvas.removeEventListener("pointerdown", this.onPointerDown);
|
||||
this.canvas.removeEventListener("dblclick", this.onDoubleClick);
|
||||
window.removeEventListener("pointermove", this.onPointerMove);
|
||||
window.removeEventListener("pointerup", this.onPointerUp);
|
||||
}
|
||||
|
||||
/** Converts viewport coordinates to the canvas's device-pixel space. */
|
||||
private toDevice(clientX: number, clientY: number): { x: number; y: number } {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
return { x: (clientX - rect.left) * dpr, y: (clientY - rect.top) * dpr };
|
||||
}
|
||||
|
||||
private onWheel = (e: WheelEvent): void => {
|
||||
e.preventDefault();
|
||||
// Trackpad pinch arrives as a wheel event with ctrlKey set; ⌘/ctrl+scroll
|
||||
// zooms too. A plain scroll pans.
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
const { x, y } = this.toDevice(e.clientX, e.clientY);
|
||||
this.camera.zoomAt(Math.exp(-e.deltaY * ZOOM_SENSITIVITY), x, y);
|
||||
} else {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
this.camera.panBy(-e.deltaX * dpr, -e.deltaY * dpr);
|
||||
}
|
||||
this.onCameraChanged();
|
||||
};
|
||||
|
||||
private onPointerDown = (e: PointerEvent): void => {
|
||||
if (e.button !== 0 && e.button !== 1) return; // left or middle drag
|
||||
this.dragging = true;
|
||||
this.canvas.style.cursor = "grabbing";
|
||||
};
|
||||
|
||||
private onPointerMove = (e: PointerEvent): void => {
|
||||
if (!this.dragging) return;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
this.camera.panBy(e.movementX * dpr, e.movementY * dpr);
|
||||
this.onCameraChanged();
|
||||
};
|
||||
|
||||
private onPointerUp = (): void => {
|
||||
if (!this.dragging) return;
|
||||
this.dragging = false;
|
||||
this.canvas.style.cursor = "grab";
|
||||
};
|
||||
|
||||
private onDoubleClick = (): void => {
|
||||
this.camera.reset();
|
||||
this.onCameraChanged();
|
||||
};
|
||||
|
||||
private onCameraChanged(): void {
|
||||
this.dirty = true;
|
||||
this.callbacks.onCameraChange?.(this.camera.zoom);
|
||||
}
|
||||
|
||||
// --- Rendering ----------------------------------------------------------
|
||||
|
||||
private tick = (ts: number): void => {
|
||||
if (this.playing) {
|
||||
if (this.lastTs !== 0) {
|
||||
const dt = (ts - this.lastTs) / 1000;
|
||||
this.currentFrame += dt * this.fps;
|
||||
if (this.currentFrame >= this.totalFrames) {
|
||||
this.currentFrame %= this.totalFrames; // loop
|
||||
}
|
||||
}
|
||||
this.lastTs = ts;
|
||||
this.draw();
|
||||
this.callbacks.onFrame?.(this.currentFrame, this.totalFrames);
|
||||
} else if (this.dirty) {
|
||||
this.draw();
|
||||
}
|
||||
this.dirty = false;
|
||||
this.rafId = requestAnimationFrame(this.tick);
|
||||
};
|
||||
|
||||
private draw(): void {
|
||||
if (!this.surface) return;
|
||||
const canvas = this.surface.getCanvas();
|
||||
canvas.clear(this.ck.TRANSPARENT);
|
||||
|
||||
const [w, h] = this.animation.size();
|
||||
const cw = this.canvas.width;
|
||||
const ch = this.canvas.height;
|
||||
const scale = Math.min(cw / w, ch / h);
|
||||
const dw = w * scale;
|
||||
const dh = h * scale;
|
||||
const left = (cw - dw) / 2;
|
||||
const top = (ch - dh) / 2;
|
||||
|
||||
canvas.save();
|
||||
this.camera.apply(canvas);
|
||||
this.animation.seekFrame(this.currentFrame);
|
||||
this.animation.render(canvas, this.ck.LTRBRect(left, top, left + dw, top + dh));
|
||||
canvas.restore();
|
||||
|
||||
this.surface.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import App from "@/App";
|
||||
import "@/index.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Route, Router } from "@solidjs/router";
|
||||
import { App } from "./app";
|
||||
|
||||
export function Root() {
|
||||
return (
|
||||
<Router>
|
||||
<Route path="/" component={App} />
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface ControlMeta {
|
||||
sid: string;
|
||||
label?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
export type AnimationSlot =
|
||||
| { id: string; type: "scalar"; value: number }
|
||||
| { id: string; type: "color"; value: [number, number, number, number] }
|
||||
| { id: string; type: "vec2"; value: [number, number] }
|
||||
| { id: string; type: "text"; value: string };
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./common";
|
||||
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,10 +1,22 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
],
|
||||
"compilerOptions": {
|
||||
// General
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
"target": "ESNext",
|
||||
|
||||
// Modules
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmit": true,
|
||||
|
||||
// Type Checking & Safety
|
||||
"strict": true,
|
||||
"types": ["vite/client"],
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,33 +1,23 @@
|
||||
import path from "node:path";
|
||||
import { defineConfig, type Plugin } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// Files in /public live outside Vite's module graph, so editing them doesn't
|
||||
// trigger HMR on its own. This plugin watches the Lottie file and its optional
|
||||
// controls sidecar explicitly and full-reloads the page on change — so an LLM
|
||||
// (or you) can overwrite either and immediately see the result in the running
|
||||
// dev server.
|
||||
function watchLottie(): Plugin {
|
||||
const files = [
|
||||
path.resolve(__dirname, "public/lottie.json"),
|
||||
path.resolve(__dirname, "public/controls.json"),
|
||||
];
|
||||
return {
|
||||
name: "watch-lottie",
|
||||
configureServer(server) {
|
||||
files.forEach((file) => server.watcher.add(file));
|
||||
server.watcher.on("change", (changed) => {
|
||||
if (files.includes(path.resolve(changed))) {
|
||||
server.ws.send({ type: "full-reload" });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import path from "node:path"
|
||||
import { defineConfig } from 'vite';
|
||||
import solidSvg from 'vite-plugin-solid-svg';
|
||||
import solidPlugin from 'vite-plugin-solid';
|
||||
import devtools from 'solid-devtools/vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss(), watchLottie()],
|
||||
plugins: [
|
||||
devtools(),
|
||||
solidPlugin(),
|
||||
tailwindcss(),
|
||||
solidSvg({ defaultAsComponent: true }),
|
||||
],
|
||||
server: {
|
||||
port: 3030,
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
|
||||