diff --git a/f0ck-svelte/.gitignore b/f0ck-svelte/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/f0ck-svelte/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/f0ck-svelte/.npmrc b/f0ck-svelte/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/f0ck-svelte/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/f0ck-svelte/.vscode/extensions.json b/f0ck-svelte/.vscode/extensions.json new file mode 100644 index 0000000..28d1e67 --- /dev/null +++ b/f0ck-svelte/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/f0ck-svelte/README.md b/f0ck-svelte/README.md new file mode 100644 index 0000000..b7a6e3a --- /dev/null +++ b/f0ck-svelte/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +npx sv@0.16.3 create --template minimal --types ts --install npm . +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/f0ck-svelte/package-lock.json b/f0ck-svelte/package-lock.json new file mode 100644 index 0000000..f31c1dc --- /dev/null +++ b/f0ck-svelte/package-lock.json @@ -0,0 +1,2486 @@ +{ + "name": "f0ck-svelte", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "f0ck-svelte", + "version": "0.0.1", + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.1", + "@sveltejs/adapter-node": "^5.5.7", + "@sveltejs/kit": "^2.63.0", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tailwindcss/vite": "^4.3.3", + "@types/node": "^26.1.1", + "daisyui": "^5.6.18", + "svelte": "^5.56.1", + "svelte-check": "^4.6.0", + "tailwindcss": "^4.3.3", + "typescript": "^6.0.3", + "vite": "^8.0.16" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.3.tgz", + "integrity": "sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz", + "integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz", + "integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/adapter-node": { + "version": "5.5.7", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.5.7.tgz", + "integrity": "sha512-uOfc9eVlI3A37RRSaKcgrheBYPrfJwC9VMqDp8x/O6tlKdcLLvHThSWD0KNIbjQ/d+7bwLGx3vx6aowAcRfd2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-commonjs": "^29.0.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-replace": "^6.0.3", + "rollup": "^4.59.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.4.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.70.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.70.1.tgz", + "integrity": "sha512-nY9SPHGOZro3doud9vZXDBwl9tCZIouuJztjgSHs6PAIrv9M/z5O7eOhPV5xU7CgVHA976Jwu3BA1hIFvXztkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.9", + "@types/cookie": "^0.6.0", + "acorn": "^8.16.0", + "cookie": "^0.6.0", + "devalue": "^5.8.1", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.0.tgz", + "integrity": "sha512-1LgZ/qUqSoq+QorD83lk2hka79Px0wXNW2q5V1nZlxGhQgw1jrsIbVz5YiCeucVLo4XvFLjXukUaQjIiqowkcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.2.0.tgz", + "integrity": "sha512-1SpkuMSRLfugrVX+IrKfE1RUegzo8AQzKQ6qQPfVzbcWi5IhuTPaKb5ZrLpucleFznkc4/RTeSPoRnGWFxX+EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/daisyui": { + "version": "5.6.18", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.6.18.tgz", + "integrity": "sha512-6y9rboRQl8fosnusy/5pg11yp+H0LtK3YdtfS4Vf2QVnaYOKasNdYm6hDxeiT6bwZPdl4WRPzVIlIApdo2ssBg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.0.tgz", + "integrity": "sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svelte": { + "version": "5.56.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.6.tgz", + "integrity": "sha512-p4HDLDogGHKRKCrgckQHNs5PEfXkju6JI5jTywueaKJI5hAdjPohEhRtQ0M1SWC/+TA73SPln+r7srr+7e4nZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.12", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.3.tgz", + "integrity": "sha512-DHdTCGX62R0fCxBEaT+USdASAnoaRBaaNczkRJl0K7o3WyoCeVUbVxo6fKqpOll/B+WMWCsiFK0eFrJSNBKZIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.0", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/f0ck-svelte/package.json b/f0ck-svelte/package.json new file mode 100644 index 0000000..74995cb --- /dev/null +++ b/f0ck-svelte/package.json @@ -0,0 +1,28 @@ +{ + "name": "f0ck-svelte", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^7.0.1", + "@sveltejs/adapter-node": "^5.5.7", + "@sveltejs/kit": "^2.63.0", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tailwindcss/vite": "^4.3.3", + "@types/node": "^26.1.1", + "daisyui": "^5.6.18", + "svelte": "^5.56.1", + "svelte-check": "^4.6.0", + "tailwindcss": "^4.3.3", + "typescript": "^6.0.3", + "vite": "^8.0.16" + } +} diff --git a/f0ck-svelte/src/app.css b/f0ck-svelte/src/app.css new file mode 100644 index 0000000..8dbd53e --- /dev/null +++ b/f0ck-svelte/src/app.css @@ -0,0 +1,77 @@ +@import "tailwindcss"; +@plugin "daisyui" { + themes: f0ck --default, light; +} + +@theme { + /* f0ck dark theme — AMOLED-inspired */ + --color-base-100: oklch(10% 0.02 265); + --color-base-200: oklch(13% 0.02 265); + --color-base-300: oklch(16% 0.02 265); + --color-base-content: oklch(92% 0.01 265); + + --color-primary: oklch(55% 0.22 265); + --color-primary-content: oklch(98% 0.01 265); + + --color-secondary: oklch(60% 0.18 300); + --color-secondary-content: oklch(98% 0.01 265); + + --color-accent: oklch(72% 0.15 290); + --color-accent-content: oklch(10% 0.02 265); + + --color-neutral: oklch(20% 0.02 265); + --color-neutral-content: oklch(85% 0.01 265); + + --color-info: oklch(70% 0.15 220); + --color-success: oklch(70% 0.15 160); + --color-warning: oklch(78% 0.18 80); + --color-error: oklch(63% 0.22 25); + + /* Rating colors */ + --color-sfw: oklch(70% 0.15 160); + --color-nsfw: oklch(65% 0.20 25); + --color-nsfl: oklch(60% 0.22 0); +} + +/* ── Global resets & base styles ───────────────────────────────────────── */ +*, *::before, *::after { + box-sizing: border-box; +} + +html { + font-family: 'Inter', system-ui, -apple-system, sans-serif; + background-color: var(--color-base-100); + color: var(--color-base-content); + scroll-behavior: smooth; +} + +body { + min-height: 100dvh; + overscroll-behavior: none; +} + +/* ── Scrollbar styling ─────────────────────────────────────────────────── */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: oklch(30% 0.02 265); border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: oklch(40% 0.02 265); } + +/* ── Rating badge utility classes ─────────────────────────────────────── */ +.badge-sfw { background: oklch(70% 0.15 160 / 20%); color: oklch(70% 0.15 160); border-color: oklch(70% 0.15 160 / 40%); } +.badge-nsfw { background: oklch(65% 0.20 25 / 20%); color: oklch(65% 0.20 25); border-color: oklch(65% 0.20 25 / 40%); } +.badge-nsfl { background: oklch(55% 0.22 0 / 20%); color: oklch(55% 0.22 0); border-color: oklch(55% 0.22 0 / 40%); } +.badge-unt { background: oklch(55% 0.02 265 / 20%); color: oklch(65% 0.02 265); border-color: oklch(55% 0.02 265 / 40%); } + +/* ── Grid thumb skeleton pulse ─────────────────────────────────────────── */ +@keyframes thumb-pulse { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 0.7; } +} +.thumb-skeleton { animation: thumb-pulse 1.5s ease-in-out infinite; } + +/* ── Fade-in for navigations ───────────────────────────────────────────── */ +@keyframes fade-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } +} +.page-enter { animation: fade-in 0.15s ease-out; } diff --git a/f0ck-svelte/src/app.d.ts b/f0ck-svelte/src/app.d.ts new file mode 100644 index 0000000..065cd7c --- /dev/null +++ b/f0ck-svelte/src/app.d.ts @@ -0,0 +1,21 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +import type { SessionUser } from '$lib/types/session'; + +declare global { + namespace App { + interface Error { + message: string; + code?: number; + } + interface Locals { + session: SessionUser | null; + } + interface PageData { + session: SessionUser | null; + } + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/f0ck-svelte/src/app.html b/f0ck-svelte/src/app.html new file mode 100644 index 0000000..6a2bb58 --- /dev/null +++ b/f0ck-svelte/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/f0ck-svelte/src/hooks.server.ts b/f0ck-svelte/src/hooks.server.ts new file mode 100644 index 0000000..15852fc --- /dev/null +++ b/f0ck-svelte/src/hooks.server.ts @@ -0,0 +1,41 @@ +import type { Handle } from '@sveltejs/kit'; + +/** + * Server-side hook: read session from the existing f0ckm backend + * by forwarding the session cookie on every SSR request. + * + * This keeps auth as a single source of truth in the backend. + */ +export const handle: Handle = async ({ event, resolve }) => { + // Forward cookies to the backend session endpoint + const cookieHeader = event.request.headers.get('cookie') ?? ''; + + try { + const res = await fetch(`${getApiBase(event)}/api/v2/session`, { + headers: { cookie: cookieHeader } + }); + + if (res.ok) { + const data = await res.json(); + event.locals.session = data.loggedIn ? data.user : null; + } else { + event.locals.session = null; + } + } catch { + // Backend not reachable (dev without backend running) + event.locals.session = null; + } + + return resolve(event); +}; + +/** + * Determine the API base URL for server-side fetches. + * In production the SvelteKit server and the API backend run on separate ports. + * The VITE_API_URL env var (or API_URL for SSR) sets the backend address. + */ +function getApiBase(event: Parameters[0]['event']): string { + // SSR: use internal backend address (avoids going through Nginx proxy) + const internal = process.env.API_URL ?? 'http://localhost:1337'; + return internal; +} diff --git a/f0ck-svelte/src/lib/api/actions.ts b/f0ck-svelte/src/lib/api/actions.ts new file mode 100644 index 0000000..fa0f161 --- /dev/null +++ b/f0ck-svelte/src/lib/api/actions.ts @@ -0,0 +1,132 @@ +import type { Comment } from '$lib/types/comment'; +import { csrfFetch } from '$lib/utils/csrfFetch'; + +// ── Comments ────────────────────────────────────────────────────────────────── + +export interface CommentsResponse { + success: boolean; + comments: Comment[]; + total?: number; +} + +export async function getComments( + itemId: number, + fetchFn = fetch +): Promise { + const res = await fetchFn(`/api/comments/${itemId}`); + if (!res.ok) throw new Error(`getComments failed: ${res.status}`); + return res.json(); +} + +export async function postComment(payload: { + itemId: number; + content: string; + replyTo?: number | null; +}): Promise<{ success: boolean; comment?: Comment; message?: string; msg?: string }> { + const body = new URLSearchParams({ + item_id: String(payload.itemId), + content: payload.content, + ...(payload.replyTo ? { parent_id: String(payload.replyTo) } : {}) + }); + const res = await csrfFetch('/api/comments', { method: 'POST', body }); + if (!res.ok && res.status !== 400 && res.status !== 429) throw new Error(`postComment failed: ${res.status}`); + return res.json(); +} + +export async function deleteComment( + commentId: number +): Promise<{ success: boolean; msg?: string }> { + // Backend uses POST /api/comments/:id/delete (not DELETE method) + const res = await csrfFetch(`/api/comments/${commentId}/delete`, { method: 'POST' }); + if (!res.ok) throw new Error(`deleteComment failed: ${res.status}`); + return res.json(); +} + +// ── Favorites ───────────────────────────────────────────────────────────────── + +export interface FavResponse { + success: boolean; + itemid: number; + favs: Array<{ + user: string; + display_name: string | null; + avatar: string | null; + avatar_file: string | null; + username_color: string | null; + }>; +} + +export async function toggleFav(itemId: number): Promise { + const body = new URLSearchParams({ postid: String(itemId) }); + const res = await csrfFetch('/api/v2/togglefav', { method: 'POST', body }); + if (!res.ok) throw new Error(`toggleFav failed: ${res.status}`); + return res.json(); +} + +// ── Tags ────────────────────────────────────────────────────────────────────── + +export interface TagsResponse { + success: boolean; + tags: import('$lib/types/item').Tag[]; + msg?: string; +} + +export async function addTag(itemId: number, tagname: string): Promise { + const body = new URLSearchParams({ tagname }); + const res = await csrfFetch(`/api/v2/tags/${itemId}`, { method: 'POST', body }); + if (!res.ok && res.status !== 400) throw new Error(`addTag failed: ${res.status}`); + return res.json(); +} + +export async function deleteTag(itemId: number, tagname: string): Promise { + const res = await csrfFetch(`/api/v2/tags/${itemId}/${encodeURIComponent(tagname)}`, { + method: 'DELETE' + }); + if (!res.ok) throw new Error(`deleteTag failed: ${res.status}`); + return res.json(); +} + +export interface CycleRatingResponse { + success: boolean; + rating_tag_id: number; + rating_label: 'SFW' | 'NSFW' | 'NSFL'; + rating_class: 'sfw' | 'nsfw' | 'nsfl'; + msg?: string; +} + +export async function cycleRating(itemId: number): Promise { + const res = await csrfFetch(`/api/v2/tags/${itemId}/cycle-rating`, { method: 'PUT' }); + if (!res.ok) throw new Error(`cycleRating failed: ${res.status}`); + return res.json(); +} + +export async function suggestTags(query: string): Promise<{ success: boolean; suggestions: Record }> { + const res = await fetch(`/api/v2/tags/suggest?q=${encodeURIComponent(query)}`); + if (!res.ok) throw new Error('suggestTags failed'); + return res.json(); +} + +// ── Item management (mod/admin) ─────────────────────────────────────────────── + +export async function deleteItem( + itemId: number, + reason = '' +): Promise<{ success: boolean; msg?: string }> { + const body = new URLSearchParams({ postid: String(itemId), reason }); + const res = await csrfFetch('/api/v2/admin/deletepost', { method: 'POST', body }); + if (!res.ok) throw new Error(`deleteItem failed: ${res.status}`); + return res.json(); +} + +export async function pinItem(itemId: number): Promise<{ success: boolean; pinned: boolean }> { + // Backend reads id from query string: req.url.qs.id + const res = await csrfFetch(`/mod/pin/?id=${itemId}`, { method: 'POST' }); + if (!res.ok) throw new Error(`pinItem failed: ${res.status}`); + return res.json(); +} + +export async function unpinItem(itemId: number): Promise<{ success: boolean; pinned: boolean }> { + const res = await csrfFetch(`/mod/unpin/?id=${itemId}`, { method: 'POST' }); + if (!res.ok) throw new Error(`unpinItem failed: ${res.status}`); + return res.json(); +} diff --git a/f0ck-svelte/src/lib/api/auth.ts b/f0ck-svelte/src/lib/api/auth.ts new file mode 100644 index 0000000..37edb27 --- /dev/null +++ b/f0ck-svelte/src/lib/api/auth.ts @@ -0,0 +1,29 @@ +import type { SessionResponse } from '$lib/types/session'; + +export async function getSession(fetchFn: typeof fetch = fetch): Promise { + const res = await fetchFn('/api/v2/session'); + if (!res.ok) return { loggedIn: false, user: null }; + return res.json(); +} + +export async function login( + username: string, + password: string, + kmsi = false +): Promise<{ success: boolean; msg?: string }> { + const body = new URLSearchParams({ username, password, ...(kmsi ? { kmsi: '1' } : {}) }); + const res = await fetch('/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json' + }, + body: body.toString(), + credentials: 'include' + }); + return res.json(); +} + +export async function logout(): Promise { + await fetch('/logout', { method: 'POST', credentials: 'include' }); +} diff --git a/f0ck-svelte/src/lib/api/items.ts b/f0ck-svelte/src/lib/api/items.ts new file mode 100644 index 0000000..ff47ce4 --- /dev/null +++ b/f0ck-svelte/src/lib/api/items.ts @@ -0,0 +1,96 @@ +import type { ItemsResponse, ItemResponse, RandomResponse } from '$lib/types/item'; + +export interface GetItemsParams { + page?: number; + mode?: number; + ratings?: string; + mime?: string; + tag?: string; + hall?: string; + user?: string; + fav?: boolean; + random?: boolean; + strict?: boolean; + min_xd?: number; + newer?: number; + tagger?: string; +} + +/** + * Fetch a page of grid items. + * Called from +page.ts (server-side) and from the client for infinite-scroll. + */ +export async function getItems( + params: GetItemsParams, + fetchFn: typeof fetch = fetch +): Promise { + const qs = new URLSearchParams(); + if (params.page) qs.set('page', String(params.page)); + if (params.mode !== undefined) qs.set('mode', String(params.mode)); + if (params.ratings) qs.set('ratings', params.ratings); + if (params.mime) qs.set('mime', params.mime); + if (params.tag) qs.set('tag', params.tag); + if (params.hall) qs.set('hall', params.hall); + if (params.user) qs.set('user', params.user); + if (params.fav) qs.set('fav', 'true'); + if (params.random) qs.set('random', '1'); + if (params.strict) qs.set('strict', '1'); + if (params.min_xd) qs.set('min_xd', String(params.min_xd)); + if (params.newer) qs.set('newer', String(params.newer)); + if (params.tagger) qs.set('tagger', params.tagger); + + const res = await fetchFn(`/api/v2/items?${qs}`); + if (!res.ok) throw new Error(`Failed to fetch items: ${res.status}`); + return res.json(); +} + +export interface GetItemParams { + mode?: number; + ratings?: string; + tag?: string; + hall?: string; + user?: string; + fav?: boolean; + random?: boolean; + strict?: boolean; + mime?: string; + userHall?: string; + userHallOwner?: string; +} + +/** + * Fetch a single item with its neighbours and metadata. + */ +export async function getItem( + id: number | string, + params: GetItemParams = {}, + fetchFn: typeof fetch = fetch +): Promise { + const qs = new URLSearchParams(); + if (params.mode !== undefined) qs.set('mode', String(params.mode)); + if (params.ratings) qs.set('ratings', params.ratings); + if (params.tag) qs.set('tag', params.tag); + if (params.hall) qs.set('hall', params.hall); + if (params.user) qs.set('user', params.user); + if (params.fav) qs.set('fav', 'true'); + if (params.random) qs.set('random', '1'); + if (params.strict) qs.set('strict', '1'); + if (params.mime) qs.set('mime', params.mime); + if (params.userHall) qs.set('userHall', params.userHall); + if (params.userHallOwner) qs.set('userHallOwner', params.userHallOwner); + + const suffix = qs.toString() ? `?${qs}` : ''; + const res = await fetchFn(`/api/v2/items/${id}${suffix}`); + if (!res.ok) throw new Error(`Failed to fetch item ${id}: ${res.status}`); + return res.json(); +} + +/** + * Get a random item ID (optionally within a context). + * Returns the JSON response; caller should navigate to response.url. + */ +export async function getRandom(fetchFn: typeof fetch = fetch): Promise { + const res = await fetchFn('/api/v2/random'); + if (!res.ok) throw new Error(`Failed to get random item: ${res.status}`); + return res.json(); +} diff --git a/f0ck-svelte/src/lib/assets/favicon.svg b/f0ck-svelte/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/f0ck-svelte/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/f0ck-svelte/src/lib/components/item/Comments.svelte b/f0ck-svelte/src/lib/components/item/Comments.svelte new file mode 100644 index 0000000..ce163cd --- /dev/null +++ b/f0ck-svelte/src/lib/components/item/Comments.svelte @@ -0,0 +1,232 @@ + + +
+ +
+ + Comments {#if comments.length > 0}({comments.length}){/if} + + +
+ + + {#if $session} +
+ {#if replyTo} +
+ + Replying to {replyTo.display_name || replyTo.username} + +
+ {/if} +
+ + +
+

Ctrl+Enter to submit

+
+ {:else} +

+ Log in to comment. +

+ {/if} + + + {#if loading} +
+ +
+ {:else if error} +

{error}

+ {:else if comments.length === 0} +

No comments yet.

+ {:else} +
    + {#each comments as comment (comment.id)} +
  • + + +
    +
    + {comment.username} +
    +
    +
    + + +
    + + + {#if comment.is_deleted} +

    — deleted —

    + {:else} + +
    + {@html comment.content} +
    + {/if} + + + {#if !comment.is_deleted} +
    + {#if $session} + + {/if} + {#if canDelete(comment)} + + {/if} +
    + {/if} +
    +
  • + {/each} +
+ {/if} +
diff --git a/f0ck-svelte/src/lib/components/item/ItemCard.svelte b/f0ck-svelte/src/lib/components/item/ItemCard.svelte new file mode 100644 index 0000000..10d0536 --- /dev/null +++ b/f0ck-svelte/src/lib/components/item/ItemCard.svelte @@ -0,0 +1,83 @@ + + + + +
+ Item {item.id} { (e.target as HTMLImageElement).src = '/s/img/404.gif'; }} + /> +
+ + +
+
+ #{item.id} +
+ {#if item.is_oc} + OC + {/if} + {#if item.is_pinned} + + {/if} + {#if item.xd_tier > 0} + {item.xd_label} + {/if} +
+
+ {#if item.display_name || item.username} + + {item.display_name || item.username} + + {/if} +
+ + + {#if item.tag} +
+ {item.tag} +
+ {/if} + + + {#if item.has_notification} +
+ {/if} +
diff --git a/f0ck-svelte/src/lib/components/item/ItemView.svelte b/f0ck-svelte/src/lib/components/item/ItemView.svelte new file mode 100644 index 0000000..aeee529 --- /dev/null +++ b/f0ck-svelte/src/lib/components/item/ItemView.svelte @@ -0,0 +1,503 @@ + + + + +
+ + + + + +
+ + {#if item.title} +

{item.title}

+ {/if} + + + + + +
+
+ + + + +
+ + +
+ + #{item.id} + + {#if isMod} + + {:else} + {ratingLabel} + {/if} + + {item.author_display_name || item.username} + + {#if isOc} + OC + {/if} + {#if isPinned} + + {/if} + {#if item.primaryHall} + + + {item.primaryHall.name} + + {/if} + +
+ + +
+ + + + + + + + + + + {#if canManage} +
+ + + + +
+ {/if} +
+ + + {#if favorites.length > 0} + + {/if} + +
+
+
+ +
diff --git a/f0ck-svelte/src/lib/components/item/TagList.svelte b/f0ck-svelte/src/lib/components/item/TagList.svelte new file mode 100644 index 0000000..d1bab60 --- /dev/null +++ b/f0ck-svelte/src/lib/components/item/TagList.svelte @@ -0,0 +1,37 @@ + + +
+ {#each tags as tag (tag.id)} + + + {tag.tag} + + {#if canRemove && onRemove} + + {/if} + + {/each} +
diff --git a/f0ck-svelte/src/lib/components/layout/Navbar.svelte b/f0ck-svelte/src/lib/components/layout/Navbar.svelte new file mode 100644 index 0000000..08d215c --- /dev/null +++ b/f0ck-svelte/src/lib/components/layout/Navbar.svelte @@ -0,0 +1,100 @@ + + + diff --git a/f0ck-svelte/src/lib/components/media/AudioPlayer.svelte b/f0ck-svelte/src/lib/components/media/AudioPlayer.svelte new file mode 100644 index 0000000..4dd2218 --- /dev/null +++ b/f0ck-svelte/src/lib/components/media/AudioPlayer.svelte @@ -0,0 +1,24 @@ + + +
+ +
+ + +
diff --git a/f0ck-svelte/src/lib/components/media/ImageViewer.svelte b/f0ck-svelte/src/lib/components/media/ImageViewer.svelte new file mode 100644 index 0000000..b013564 --- /dev/null +++ b/f0ck-svelte/src/lib/components/media/ImageViewer.svelte @@ -0,0 +1,58 @@ + + + + +
+ + +
+ + +{#if expanded} + +
+ + +
+{/if} diff --git a/f0ck-svelte/src/lib/components/media/VideoPlayer.svelte b/f0ck-svelte/src/lib/components/media/VideoPlayer.svelte new file mode 100644 index 0000000..0c52b13 --- /dev/null +++ b/f0ck-svelte/src/lib/components/media/VideoPlayer.svelte @@ -0,0 +1,36 @@ + + + + +
+ + +
diff --git a/f0ck-svelte/src/lib/components/media/YouTubeEmbed.svelte b/f0ck-svelte/src/lib/components/media/YouTubeEmbed.svelte new file mode 100644 index 0000000..a29962f --- /dev/null +++ b/f0ck-svelte/src/lib/components/media/YouTubeEmbed.svelte @@ -0,0 +1,18 @@ + + +
+ +
diff --git a/f0ck-svelte/src/lib/components/ui/FlashMessages.svelte b/f0ck-svelte/src/lib/components/ui/FlashMessages.svelte new file mode 100644 index 0000000..5276444 --- /dev/null +++ b/f0ck-svelte/src/lib/components/ui/FlashMessages.svelte @@ -0,0 +1,42 @@ + + +
+ {#each $flashes as msg (msg.id)} + + {/each} +
+ + diff --git a/f0ck-svelte/src/lib/components/ui/Pagination.svelte b/f0ck-svelte/src/lib/components/ui/Pagination.svelte new file mode 100644 index 0000000..a00e68a --- /dev/null +++ b/f0ck-svelte/src/lib/components/ui/Pagination.svelte @@ -0,0 +1,62 @@ + + +{#if pagination.end > 1} + +{/if} diff --git a/f0ck-svelte/src/lib/index.ts b/f0ck-svelte/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/f0ck-svelte/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/f0ck-svelte/src/lib/stores/flash.ts b/f0ck-svelte/src/lib/stores/flash.ts new file mode 100644 index 0000000..759b885 --- /dev/null +++ b/f0ck-svelte/src/lib/stores/flash.ts @@ -0,0 +1,28 @@ +import { writable } from 'svelte/store'; + +// ── Flash message type ──────────────────────────────────────────────────────── +export type FlashType = 'info' | 'success' | 'warning' | 'error'; + +export interface FlashMessage { + id: number; + text: string; + type: FlashType; +} + +let _nextId = 0; +export const flashes = writable([]); + +/** + * Show a flash message. Auto-dismisses after `duration` ms. + * Returns the message ID so callers can dismiss early if needed. + */ +export function flash(text: string, type: FlashType = 'info', duration = 2500): number { + const id = ++_nextId; + flashes.update((q) => [...q, { id, text, type }]); + setTimeout(() => dismiss(id), duration); + return id; +} + +export function dismiss(id: number): void { + flashes.update((q) => q.filter((m) => m.id !== id)); +} diff --git a/f0ck-svelte/src/lib/stores/pin.ts b/f0ck-svelte/src/lib/stores/pin.ts new file mode 100644 index 0000000..30469f2 --- /dev/null +++ b/f0ck-svelte/src/lib/stores/pin.ts @@ -0,0 +1,20 @@ +/** + * Lightweight broadcast store for pin/unpin events. + * ItemView fires it after a successful pin action; + * the main grid subscribes to patch the item card immediately. + */ +import { writable } from 'svelte/store'; + +export interface PinEvent { + itemId: number; + pinned: boolean; +} + +// Null when idle, set to an event when pin/unpin succeeds +export const pinEvent = writable(null); + +export function broadcastPin(itemId: number, pinned: boolean) { + pinEvent.set({ itemId, pinned }); + // Reset so subsequent identical events still trigger subscribers + setTimeout(() => pinEvent.set(null), 0); +} diff --git a/f0ck-svelte/src/lib/stores/preferences.ts b/f0ck-svelte/src/lib/stores/preferences.ts new file mode 100644 index 0000000..b503c6f --- /dev/null +++ b/f0ck-svelte/src/lib/stores/preferences.ts @@ -0,0 +1,50 @@ +import { writable, derived, get } from 'svelte/store'; + +// ── Content mode ───────────────────────────────────────────────────────────── +// 0=SFW, 1=NSFW, 2=untagged, 3=ALL, 4=NSFL +export const activeMode = writable(3); + +// ── Multi-rating filter ─────────────────────────────────────────────────────── +// When non-empty, overrides activeMode for filtering (OR logic across selected ratings) +export type Rating = 'sfw' | 'nsfw' | 'nsfl' | 'untagged'; +export const activeRatings = writable([]); + +// ── MIME filter ─────────────────────────────────────────────────────────────── +export type MimeFilter = 'video' | 'audio' | 'image' | 'flash'; +export const activeMimes = writable([]); + +// ── Derived: ratings query param string for API calls ───────────────────────── +export const ratingsParam = derived(activeRatings, ($r) => $r.join('|') || null); + +// ── Derived: readable label for the filter badge ───────────────────────────── +export const filterLabel = derived( + [activeMode, activeRatings], + ([$mode, $ratings]) => { + if ($ratings.length > 0) { + const abbr: Record = { sfw: 'S', nsfw: 'N', nsfl: 'L', untagged: 'U' }; + return $ratings.map((r) => abbr[r]).join('+'); + } + const labels: Record = { 0: 'SFW', 1: 'NSFW', 2: 'UNT', 3: 'ALL', 4: 'NSFL' }; + return labels[$mode] ?? 'ALL'; + } +); + +// ── Persistence helpers ─────────────────────────────────────────────────────── +// Call these after receiving the session from the server to hydrate from cookie/session. +export function initPreferences(opts: { mode: number; ratings?: string }) { + activeMode.set(opts.mode ?? 3); + if (opts.ratings) { + const parsed = opts.ratings.split(/[|,]/).filter((r): r is Rating => + ['sfw', 'nsfw', 'nsfl', 'untagged'].includes(r) + ); + activeRatings.set(parsed); + } +} + +/** Build a mode number → server mode number (for API calls when using multi-rating). */ +export function getServerMode(): number { + const ratings = get(activeRatings); + if (ratings.length === 0) return get(activeMode); + const single: Record = { sfw: 0, nsfw: 1, nsfl: 4, untagged: 2 }; + return ratings.length === 1 ? (single[ratings[0]] ?? 3) : 3; +} diff --git a/f0ck-svelte/src/lib/stores/session.ts b/f0ck-svelte/src/lib/stores/session.ts new file mode 100644 index 0000000..c545e69 --- /dev/null +++ b/f0ck-svelte/src/lib/stores/session.ts @@ -0,0 +1,11 @@ +import { writable, derived } from 'svelte/store'; +import type { SessionUser } from '$lib/types/session'; + +/** The current authenticated user, or null if not logged in. */ +export const session = writable(null); + +/** Convenience derived — true when logged in. */ +export const loggedIn = derived(session, ($s) => $s !== null); + +/** True when the user has admin or moderator role. */ +export const isModOrAdmin = derived(session, ($s) => !!($s?.admin || $s?.is_moderator)); diff --git a/f0ck-svelte/src/lib/stores/sse.ts b/f0ck-svelte/src/lib/stores/sse.ts new file mode 100644 index 0000000..1580dad --- /dev/null +++ b/f0ck-svelte/src/lib/stores/sse.ts @@ -0,0 +1,126 @@ +/** + * SSE store — connects to the backend's /api/notifications/stream endpoint. + * + * Emits typed events that components subscribe to with `onSSE()`. + * Auto-reconnects with exponential backoff on disconnect. + * Client-only: never runs during SSR. + */ +import { writable, get } from 'svelte/store'; +import { browser } from '$app/environment'; + +// ── Event types broadcast by the backend ───────────────────────────────────── +export interface SSEComment { + type: 'comment' | 'delete'; + id?: number; + item_id: number; + comment_id?: number; + body?: string; + username?: string; + display_name?: string | null; + avatar?: string | null; + avatar_file?: string | null; + username_color?: string | null; + created_at?: string; + parent_id?: number | null; + xd_score?: number | null; +} + +export interface SSEFavorites { + item_id: number; + favs: Array<{ + user: string; + display_name: string | null; + avatar: string | null; + avatar_file: string | null; + username_color: string | null; + }>; +} + +export interface SSETags { + item_id: number; + fresh: boolean; + tags: unknown[]; +} + +export interface SSENewItem { + id: number; + mime?: string; +} + +export interface SSEDeleteItem { + id: number; +} + +export interface SSEEvent { + type: 'comments' | 'favorites' | 'tags' | 'new_item' | 'delete_item' | 'notify' | 'activity' | string; + data: SSEComment | SSEFavorites | SSETags | SSENewItem | SSEDeleteItem | Record; +} + +// ── Connection state ────────────────────────────────────────────────────────── +export type SSEState = 'disconnected' | 'connecting' | 'connected' | 'error'; +export const sseState = writable('disconnected'); + +// ── Listener registry ───────────────────────────────────────────────────────── +type SSEListener = (event: SSEEvent) => void; +const listeners = new Set(); + +/** Subscribe to SSE events. Returns an unsubscribe function. */ +export function onSSE(fn: SSEListener): () => void { + listeners.add(fn); + return () => listeners.delete(fn); +} + +// ── Internal connection state ───────────────────────────────────────────────── +let es: EventSource | null = null; +let retryTimer: ReturnType | null = null; +let retryDelay = 2000; +const MAX_RETRY = 30_000; +const tabId = browser ? Math.random().toString(36).slice(2) : 'ssr'; + +function dispatch(raw: SSEEvent) { + for (const fn of listeners) { + try { fn(raw); } catch (e) { console.error('[SSE] listener error', e); } + } +} + +function scheduleRetry() { + if (retryTimer) clearTimeout(retryTimer); + retryTimer = setTimeout(connect, retryDelay); + retryDelay = Math.min(retryDelay * 1.5, MAX_RETRY); +} + +export function connect() { + if (!browser) return; + if (es) { es.close(); es = null; } + + sseState.set('connecting'); + es = new EventSource(`/api/notifications/stream?tabId=${tabId}`); + + es.onopen = () => { + sseState.set('connected'); + retryDelay = 2000; // reset backoff on successful connect + }; + + es.onmessage = (e) => { + try { + const data = JSON.parse(e.data) as SSEEvent; + dispatch(data); + } catch { + // ignore malformed frames + } + }; + + es.onerror = () => { + sseState.set('error'); + es?.close(); + es = null; + scheduleRetry(); + }; +} + +export function disconnect() { + if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; } + es?.close(); + es = null; + sseState.set('disconnected'); +} diff --git a/f0ck-svelte/src/lib/stores/ws.ts b/f0ck-svelte/src/lib/stores/ws.ts new file mode 100644 index 0000000..2ebce94 --- /dev/null +++ b/f0ck-svelte/src/lib/stores/ws.ts @@ -0,0 +1,81 @@ +import { writable } from 'svelte/store'; + +// ── WebSocket message types ─────────────────────────────────────────────────── +export type WSMessage = + | { type: 'pong' } + | { type: 'notification'; count: number; item?: unknown } + | { type: 'comment:new'; itemId: number; comment: unknown } + | { type: 'comment:delete'; itemId: number; commentId: number } + | { type: 'item:tag_added'; itemId: number; tag: unknown } + | { type: 'item:favorited'; itemId: number; user: string } + | { type: 'global:activity'; payload: unknown }; + +// ── Store ───────────────────────────────────────────────────────────────────── +export const wsConnected = writable(false); + +let _socket: WebSocket | null = null; +let _reconnectTimer: ReturnType | null = null; +let _reconnectDelay = 1000; + +const _listeners = new Map void>>(); + +/** Connect to the WebSocket server. Call once from the root layout. */ +export function wsConnect(): void { + if (typeof window === 'undefined') return; // SSR guard + if (_socket?.readyState === WebSocket.OPEN) return; + + const proto = location.protocol === 'https:' ? 'wss' : 'ws'; + _socket = new WebSocket(`${proto}://${location.host}/ws`); + + _socket.onopen = () => { + wsConnected.set(true); + _reconnectDelay = 1000; // reset backoff on successful connect + _ping(); + }; + + _socket.onmessage = (e) => { + try { + const msg = JSON.parse(e.data) as WSMessage; + // Dispatch to typed listeners + _listeners.get(msg.type)?.forEach((fn) => fn(msg)); + _listeners.get('*')?.forEach((fn) => fn(msg)); + } catch { + // ignore malformed messages + } + }; + + _socket.onclose = () => { + wsConnected.set(false); + _socket = null; + // Exponential backoff reconnect + if (_reconnectTimer) clearTimeout(_reconnectTimer); + _reconnectTimer = setTimeout(() => { + _reconnectDelay = Math.min(_reconnectDelay * 2, 30_000); + wsConnect(); + }, _reconnectDelay); + }; + + _socket.onerror = () => { + _socket?.close(); + }; +} + +/** Send a typed message to the server. */ +export function wsSend(msg: object): void { + if (_socket?.readyState === WebSocket.OPEN) { + _socket.send(JSON.stringify(msg)); + } +} + +/** Subscribe to a specific message type. Returns an unsubscribe function. */ +export function wsOn(type: string, fn: (msg: WSMessage) => void): () => void { + if (!_listeners.has(type)) _listeners.set(type, new Set()); + _listeners.get(type)!.add(fn); + return () => _listeners.get(type)?.delete(fn); +} + +let _pingTimer: ReturnType | null = null; +function _ping() { + if (_pingTimer) clearInterval(_pingTimer); + _pingTimer = setInterval(() => wsSend({ type: 'ping' }), 25_000); +} diff --git a/f0ck-svelte/src/lib/types/comment.ts b/f0ck-svelte/src/lib/types/comment.ts new file mode 100644 index 0000000..fa38cad --- /dev/null +++ b/f0ck-svelte/src/lib/types/comment.ts @@ -0,0 +1,21 @@ +export interface Comment { + id: number; + item_id: number; + username: string; + display_name: string | null; + avatar: string | null; + avatar_file: string | null; + username_color: string | null; + content: string; // may be HTML (server-rendered) + stamp: string; // human-readable relative time + stamp_full: string; // ISO timestamp + is_deleted: boolean; + reply_to: number | null; + poll?: unknown; +} + +export interface CommentsResponse { + success: boolean; + comments: Comment[]; + total?: number; +} diff --git a/f0ck-svelte/src/lib/types/item.ts b/f0ck-svelte/src/lib/types/item.ts new file mode 100644 index 0000000..c2f526f --- /dev/null +++ b/f0ck-svelte/src/lib/types/item.ts @@ -0,0 +1,137 @@ +// ── Grid / list item (returned by /api/v2/items) ──────────────────────────── +export interface GridItem { + id: number; + mime: string; + dest: string; + username: string; + display_name: string | null; + is_pinned: boolean; + is_oc: boolean; + xd_score: number; + xd_tier: number; + xd_label: string; + tag: 'SFW' | 'NSFW' | 'NSFL' | null; + tag_id: number | null; + thumb_size: 1 | 2; + has_notification: boolean; +} + +// ── Full single item (returned by /api/v2/items/:id) ──────────────────────── +export interface FullItem { + id: number; + mime: string; + dest: string; + username: string; + author_id: number | null; + author_color: string | null; + author_display_name: string | null; + author_avatar: string | null; + author_avatar_file: string | null; + title: string | null; + src: { long: string; short: string }; + thumbnail: string; + og_thumbnail: string; + og_description: string; + coverart: string; + size: string; + checksum: string | null; + timestamp: { timeago: string; timefull: string }; + favorites: FavoriteUser[]; + tags: Tag[]; + halls: Hall[]; + user_halls: Hall[]; + is_nsfw: boolean; + is_nsfl: boolean; + is_sfw: boolean; + is_pinned: boolean; + is_comments_locked: boolean; + is_oc: boolean; + is_repost: boolean; + reposts: RepostItem[]; + width: number | null; + height: number | null; + original_filename: string | null; + xd_score: number; + xd_tier: number; + xd_label: string; + primaryHall: Hall | null; + otherHalls: Hall[]; +} + +// ── Pagination ─────────────────────────────────────────────────────────────── +export interface Pagination { + prev: number | null; + next: number | null; + start: number; + end: number; + current: number; + page: number; + cheat: number[]; + location?: string; + suffix?: string; +} + +// ── Link context ───────────────────────────────────────────────────────────── +export interface ItemLink { + main: string; + mainDisplay?: string; + path: string; + suffix: string; +} + +// ── Tags ───────────────────────────────────────────────────────────────────── +export interface Tag { + id: number; + tag: string; + normalized: string; + badge: string; + display_name: string | null; + user: string; +} + +// ── Halls ──────────────────────────────────────────────────────────────────── +export interface Hall { + name: string; + slug: string; +} + +// ── Favorites ──────────────────────────────────────────────────────────────── +export interface FavoriteUser { + user: string; + avatar: string | null; + avatar_file: string | null; + username_color: string | null; + display_name: string | null; +} + +// ── Reposts ────────────────────────────────────────────────────────────────── +export interface RepostItem { + id: number; + username: string; + stamp: number; + match_type: 'checksum' | 'phash'; +} + +// ── API response wrappers ───────────────────────────────────────────────────── +export interface ItemsResponse { + success: boolean; + items: GridItem[]; + pagination: Pagination; + total: number; + link: ItemLink; +} + +export interface ItemResponse { + success: boolean; + item: FullItem | null; + pagination: Pagination; + link: ItemLink; + isSubscribed: boolean; + message?: string; +} + +export interface RandomResponse { + success: boolean; + id: number; + url: string; +} diff --git a/f0ck-svelte/src/lib/types/session.ts b/f0ck-svelte/src/lib/types/session.ts new file mode 100644 index 0000000..c7f467a --- /dev/null +++ b/f0ck-svelte/src/lib/types/session.ts @@ -0,0 +1,27 @@ +export interface SessionUser { + id: number; + user: string; + login: string; + admin: boolean; + is_moderator: boolean; + mode: number; + theme: string; + avatar: string | null; + avatar_file: string | null; + username_color: string | null; + display_name: string | null; + use_new_layout: boolean; + excluded_tags: number[]; + min_xd_score: number; + uploads_remaining: number | undefined; + pending_count?: number; + language: string | null; + disable_autoplay: boolean; + disable_swiping: boolean; + csrf_token: string; +} + +export interface SessionResponse { + loggedIn: boolean; + user: SessionUser | null; +} diff --git a/f0ck-svelte/src/lib/utils/csrfFetch.ts b/f0ck-svelte/src/lib/utils/csrfFetch.ts new file mode 100644 index 0000000..11d604e --- /dev/null +++ b/f0ck-svelte/src/lib/utils/csrfFetch.ts @@ -0,0 +1,23 @@ +/** + * Wrapper around fetch that automatically injects the CSRF token as a header + * on all state-changing requests (POST, PUT, PATCH, DELETE). + * + * The token is read from the session store at call time so it's always fresh. + */ +import { get } from 'svelte/store'; +import { session } from '$lib/stores/session'; + +const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']); + +export function csrfFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise { + const method = (init.method ?? 'GET').toUpperCase(); + if (SAFE_METHODS.has(method)) return fetch(input, init); + + const token = get(session)?.csrf_token; + if (!token) return fetch(input, init); // not logged in — let backend return 401 + + const headers = new Headers(init.headers ?? {}); + headers.set('X-CSRF-Token', token); + + return fetch(input, { ...init, headers }); +} diff --git a/f0ck-svelte/src/lib/utils/mimeHelpers.ts b/f0ck-svelte/src/lib/utils/mimeHelpers.ts new file mode 100644 index 0000000..0d734e2 --- /dev/null +++ b/f0ck-svelte/src/lib/utils/mimeHelpers.ts @@ -0,0 +1,32 @@ +/** Map a MIME type to a simple category string. */ +export function mimeCategory(mime: string): 'video' | 'audio' | 'image' | 'flash' | 'pdf' | 'archive' | 'youtube' | 'unknown' { + if (mime === 'video/youtube') return 'youtube'; + if (mime.startsWith('video/')) return 'video'; + if (mime.startsWith('audio/')) return 'audio'; + if (mime.startsWith('image/')) return 'image'; + if (mime === 'application/pdf') return 'pdf'; + if (mime.includes('flash') || mime.includes('shockwave')) return 'flash'; + if (mime.startsWith('application/')) return 'archive'; + return 'unknown'; +} + +/** Return a Font Awesome icon class for a MIME category. */ +export function mimeIcon(mime: string): string { + const cat = mimeCategory(mime); + const map: Record = { + youtube: 'fa-brands fa-youtube', + video: 'fa-solid fa-film', + audio: 'fa-solid fa-music', + image: 'fa-solid fa-image', + flash: 'fa-solid fa-bolt', + pdf: 'fa-solid fa-file-pdf', + archive: 'fa-solid fa-file-zipper', + unknown: 'fa-solid fa-file' + }; + return map[cat] ?? map.unknown; +} + +/** True if the MIME type represents inline-playable media. */ +export function isPlayable(mime: string): boolean { + return ['video', 'audio', 'youtube'].includes(mimeCategory(mime)); +} diff --git a/f0ck-svelte/src/lib/utils/timeago.ts b/f0ck-svelte/src/lib/utils/timeago.ts new file mode 100644 index 0000000..771948e --- /dev/null +++ b/f0ck-svelte/src/lib/utils/timeago.ts @@ -0,0 +1,45 @@ +/** + * Convert a Unix timestamp (seconds) or ISO string to a human-readable + * relative time string, e.g. "3 hours ago". + */ +export function timeAgo(input: number | string | Date): string { + const date = + input instanceof Date + ? input + : typeof input === 'number' + ? new Date(input * 1000) + : new Date(input); + + const now = Date.now(); + const diff = now - date.getTime(); + + const epochs: [string, number][] = [ + ['year', 365 * 24 * 3600 * 1000], + ['month', 30 * 24 * 3600 * 1000], + ['week', 7 * 24 * 3600 * 1000], + ['day', 24 * 3600 * 1000], + ['hour', 3600 * 1000], + ['minute', 60 * 1000], + ['second', 1000] + ]; + + for (const [label, ms] of epochs) { + const n = Math.floor(diff / ms); + if (n >= 1) return `${n} ${label}${n !== 1 ? 's' : ''} ago`; + } + + return 'just now'; +} + +/** + * Format an ISO timestamp to a readable full date string. + */ +export function formatDate(iso: string): string { + return new Date(iso).toLocaleString(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); +} diff --git a/f0ck-svelte/src/routes/+layout.server.ts b/f0ck-svelte/src/routes/+layout.server.ts new file mode 100644 index 0000000..63b024d --- /dev/null +++ b/f0ck-svelte/src/routes/+layout.server.ts @@ -0,0 +1,8 @@ +import type { LayoutServerLoad } from './$types'; + +/** Pass session to all pages via PageData. */ +export const load: LayoutServerLoad = ({ locals }) => { + return { + session: locals.session + }; +}; diff --git a/f0ck-svelte/src/routes/+layout.svelte b/f0ck-svelte/src/routes/+layout.svelte new file mode 100644 index 0000000..47a9b90 --- /dev/null +++ b/f0ck-svelte/src/routes/+layout.svelte @@ -0,0 +1,56 @@ + + + + f0ckm + + + + + + + +
+ +
+ {@render children()} +
+ + + +
diff --git a/f0ck-svelte/src/routes/+page.server.ts b/f0ck-svelte/src/routes/+page.server.ts new file mode 100644 index 0000000..a121a7d --- /dev/null +++ b/f0ck-svelte/src/routes/+page.server.ts @@ -0,0 +1,30 @@ +import type { PageServerLoad } from './$types'; +import { getItems } from '$lib/api/items'; + +export const load: PageServerLoad = async ({ fetch, url, locals }) => { + const page = Number(url.searchParams.get('page') ?? 1); + const mode = locals.session?.mode ?? 3; + + try { + const data = await getItems({ page, mode }, fetch); + if (data.success) { + return { + items: data.items, + pagination: data.pagination, + total: data.total, + link: data.link + }; + } + } catch (e) { + // Backend not yet running or API route not registered — return empty state + // so the page renders rather than hard-erroring. + console.warn('[/] Failed to load items:', (e as Error).message); + } + + return { + items: [], + pagination: null, + total: 0, + link: { main: '/', path: 'p/', suffix: '' } + }; +}; diff --git a/f0ck-svelte/src/routes/+page.svelte b/f0ck-svelte/src/routes/+page.svelte new file mode 100644 index 0000000..54532ae --- /dev/null +++ b/f0ck-svelte/src/routes/+page.svelte @@ -0,0 +1,79 @@ + + + + f0ckm — {total} items + + +
+ + {#if total > 0} +

{total.toLocaleString()} items

+ {/if} + + + {#if items.length > 0} +
+ {#each items as item (item.id)} + + {/each} +
+ + {#if data.pagination} + + {/if} + {:else} +
+ +

No items found.

+
+ {/if} +
diff --git a/f0ck-svelte/src/routes/[itemid]/+page.server.ts b/f0ck-svelte/src/routes/[itemid]/+page.server.ts new file mode 100644 index 0000000..d781203 --- /dev/null +++ b/f0ck-svelte/src/routes/[itemid]/+page.server.ts @@ -0,0 +1,28 @@ +import type { PageServerLoad } from './$types'; +import { getItem } from '$lib/api/items'; +import { error } from '@sveltejs/kit'; + +export const load: PageServerLoad = async ({ params, fetch, locals, url }) => { + const id = params.itemid; + if (!id || !/^\d+$/.test(id)) throw error(404, 'Not found'); + + const mode = locals.session?.mode ?? 3; + + try { + const data = await getItem(id, { mode }, fetch); + + if (!data.success || !data.item) { + throw error(404, 'Item not found or not visible in current mode'); + } + + return { + item: data.item, + pagination: data.pagination, + link: data.link, + isSubscribed: data.isSubscribed + }; + } catch (e) { + if (e instanceof Response) throw e; + throw error(500, 'Failed to load item'); + } +}; diff --git a/f0ck-svelte/src/routes/[itemid]/+page.svelte b/f0ck-svelte/src/routes/[itemid]/+page.svelte new file mode 100644 index 0000000..899a5f9 --- /dev/null +++ b/f0ck-svelte/src/routes/[itemid]/+page.svelte @@ -0,0 +1,32 @@ + + + + {ogTitle} + + + + + + + + + + + + + + diff --git a/f0ck-svelte/src/routes/login/+page.server.ts b/f0ck-svelte/src/routes/login/+page.server.ts new file mode 100644 index 0000000..c406eaa --- /dev/null +++ b/f0ck-svelte/src/routes/login/+page.server.ts @@ -0,0 +1,9 @@ +// Stub page for /login — will be built in Sprint 2. +// For now redirect to the legacy login page so auth still works. +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + // Temporary: redirect to legacy backend login until we build the Svelte login form + throw redirect(307, 'http://localhost:1337/login'); +}; diff --git a/f0ck-svelte/src/routes/random/+page.server.ts b/f0ck-svelte/src/routes/random/+page.server.ts new file mode 100644 index 0000000..02d18b2 --- /dev/null +++ b/f0ck-svelte/src/routes/random/+page.server.ts @@ -0,0 +1,16 @@ +import type { PageServerLoad } from './$types'; +import { getRandom } from '$lib/api/items'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageServerLoad = async ({ fetch }) => { + try { + const data = await getRandom(fetch); + if (data.success && data.url) { + throw redirect(307, data.url); + } + } catch (e) { + if (e instanceof Response) throw e; + } + // Fallback: go home if no random item found + throw redirect(307, '/'); +}; diff --git a/f0ck-svelte/src/routes/register/+page.server.ts b/f0ck-svelte/src/routes/register/+page.server.ts new file mode 100644 index 0000000..48aed56 --- /dev/null +++ b/f0ck-svelte/src/routes/register/+page.server.ts @@ -0,0 +1,6 @@ +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + throw redirect(307, 'http://localhost:1337/register'); +}; diff --git a/f0ck-svelte/src/routes/tag/[tag]/+page.server.ts b/f0ck-svelte/src/routes/tag/[tag]/+page.server.ts new file mode 100644 index 0000000..ce8ac7b --- /dev/null +++ b/f0ck-svelte/src/routes/tag/[tag]/+page.server.ts @@ -0,0 +1,31 @@ +import type { PageServerLoad } from './$types'; +import { getItems } from '$lib/api/items'; + +export const load: PageServerLoad = async ({ fetch, url, params, locals }) => { + const tag = decodeURIComponent(params.tag); + const page = Number(url.searchParams.get('page') ?? 1); + const mode = locals.session?.mode ?? 3; + + try { + const data = await getItems({ page, mode, tag }, fetch); + if (data.success) { + return { + tag, + items: data.items, + pagination: data.pagination, + total: data.total, + link: data.link + }; + } + } catch (e) { + console.warn(`[/tag/${tag}] Failed to load items:`, (e as Error).message); + } + + return { + tag, + items: [], + pagination: null, + total: 0, + link: { main: `/tag/${encodeURIComponent(tag)}/`, path: 'p/', suffix: '' } + }; +}; diff --git a/f0ck-svelte/src/routes/tag/[tag]/+page.svelte b/f0ck-svelte/src/routes/tag/[tag]/+page.svelte new file mode 100644 index 0000000..3597e82 --- /dev/null +++ b/f0ck-svelte/src/routes/tag/[tag]/+page.svelte @@ -0,0 +1,76 @@ + + + + #{data.tag} — f0ckm + + + +
+ + +
+
+ +

{data.tag}

+
+ {#if total > 0} + {total.toLocaleString()} + {/if} + + Clear + +
+ + + {#if items.length > 0} +
+ {#each items as item (item.id)} + + {/each} +
+ + {#if data.pagination} + + {/if} + {:else} +
+ +

No items found for #{data.tag}.

+
+ {/if} + +
diff --git a/f0ck-svelte/src/routes/tag/[tag]/[itemid]/+page.server.ts b/f0ck-svelte/src/routes/tag/[tag]/[itemid]/+page.server.ts new file mode 100644 index 0000000..b861e15 --- /dev/null +++ b/f0ck-svelte/src/routes/tag/[tag]/[itemid]/+page.server.ts @@ -0,0 +1,27 @@ +import type { PageServerLoad } from './$types'; +import { getItem } from '$lib/api/items'; +import { error } from '@sveltejs/kit'; + +export const load: PageServerLoad = async ({ fetch, params, locals }) => { + const tag = decodeURIComponent(params.tag); + const itemid = Number(params.itemid); + const mode = locals.session?.mode ?? 3; + + if (!itemid) throw error(404, 'Not found'); + + try { + const data = await getItem(itemid, { mode, tag }, fetch); + if (!data.success || !data.item) throw error(404, data.message ?? 'Item not found'); + + return { + tag, + item: data.item, + pagination: data.pagination, + link: data.link, + isSubscribed: data.isSubscribed ?? false + }; + } catch (e: unknown) { + if ((e as { status?: number })?.status) throw e; + throw error(500, 'Failed to load item'); + } +}; diff --git a/f0ck-svelte/src/routes/tag/[tag]/[itemid]/+page.svelte b/f0ck-svelte/src/routes/tag/[tag]/[itemid]/+page.svelte new file mode 100644 index 0000000..5a54c90 --- /dev/null +++ b/f0ck-svelte/src/routes/tag/[tag]/[itemid]/+page.svelte @@ -0,0 +1,31 @@ + + + + #{data.tag} — item {data.item.id} — f0ckm + + + +
+
+ f0ckm + + + + {data.tag} + + + #{data.item.id} +
+
+ + diff --git a/f0ck-svelte/static/robots.txt b/f0ck-svelte/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/f0ck-svelte/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/f0ck-svelte/tsconfig.json b/f0ck-svelte/tsconfig.json new file mode 100644 index 0000000..709cb83 --- /dev/null +++ b/f0ck-svelte/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler", + "types": ["node"] + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/f0ck-svelte/vite.config.ts b/f0ck-svelte/vite.config.ts new file mode 100644 index 0000000..9daabb1 --- /dev/null +++ b/f0ck-svelte/vite.config.ts @@ -0,0 +1,47 @@ +import adapter from '@sveltejs/adapter-node'; +import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + tailwindcss(), + sveltekit({ + compilerOptions: { + runes: ({ filename }) => + filename.split(/[/\\]/).includes('node_modules') ? undefined : true + }, + adapter: adapter() + }) + ], + server: { + port: 5173, + proxy: { + // Proxy API + WebSocket to the existing f0ckm backend + '/api': { + target: 'http://localhost:1337', + changeOrigin: true + }, + '/ajax': { + target: 'http://localhost:1337', + changeOrigin: true + }, + '/ws': { + target: 'ws://localhost:1337', + ws: true + }, + // Static media files — use exact prefix matching to avoid + // colliding with Vite's own /src/ module paths. + // Each rule anchored to exactly /X/ (with trailing slash). + '^/b/': { target: 'http://localhost:1337', changeOrigin: true }, + '^/t/': { target: 'http://localhost:1337', changeOrigin: true }, + '^/ca/': { target: 'http://localhost:1337', changeOrigin: true }, + '^/a/': { target: 'http://localhost:1337', changeOrigin: true }, + // /s/ — NOTE: must NOT match /src/ — regex anchors it + '^/s/': { target: 'http://localhost:1337', changeOrigin: true }, + '^/memes/': { target: 'http://localhost:1337', changeOrigin: true }, + // mod actions (pin, unpin, delete) + '^/mod/': { target: 'http://localhost:1337', changeOrigin: true } + } + } +}); diff --git a/src/inc/routes/apiv2/svelte.mjs b/src/inc/routes/apiv2/svelte.mjs new file mode 100644 index 0000000..67cff0d --- /dev/null +++ b/src/inc/routes/apiv2/svelte.mjs @@ -0,0 +1,212 @@ +/** + * f0ckm API v2 — JSON endpoints for the new SvelteKit frontend. + * + * These routes live alongside the existing flummpress routes and are + * purely additive — nothing in the old app is changed or removed. + * + * Mounted at: /api/v2/ + */ +import f0cklib from '../../routeinc/f0cklib.mjs'; +import cfg from '../../config.mjs'; + +const json = (res, body, code = 200) => { + res.writeHead(code, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(body)); +}; + +export default (router, _tpl) => { + + // ── GET /api/v2/session ─────────────────────────────────────────────────── + router.get('/api/v2/session', async (req, res) => { + if (!req.session) { + return json(res, { loggedIn: false, user: null }); + } + return json(res, { + loggedIn: true, + user: { + id: req.session.id, + user: req.session.user, + login: req.session.login, + admin: !!req.session.admin, + is_moderator: !!req.session.is_moderator, + mode: req.session.mode ?? 3, + theme: req.session.theme ?? 'f0ck', + avatar: req.session.avatar ?? null, + avatar_file: req.session.avatar_file ?? null, + username_color: req.session.username_color ?? null, + display_name: req.session.display_name ?? null, + use_new_layout: req.session.use_new_layout ?? true, + excluded_tags: req.session.excluded_tags ?? [], + min_xd_score: req.session.min_xd_score ?? 0, + uploads_remaining: req.session.uploads_remaining, + pending_count: req.session.pending_count, + language: req.session.language ?? null, + disable_autoplay: !!req.session.disable_autoplay, + disable_swiping: !!req.session.disable_swiping, + csrf_token: req.session.csrf_token ?? '' + } + }); + }); + + // ── GET /api/v2/items ───────────────────────────────────────────────────── + router.get('/api/v2/items', async (req, res) => { + const q = req.url.qs ?? {}; + const ratingsRaw = req.cookies?.ratings ?? q.ratings ?? null; + const ratingsArr = ratingsRaw + ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) + : null; + + const mode = q.mode !== undefined ? +q.mode : req.mode; + + try { + const data = await f0cklib.getf0cks({ + page: q.page ? +q.page : 1, + mode, + ratings: ratingsArr, + mime: q.mime ?? (req.cookies?.mime ?? null), + tag: q.tag ?? null, + hall: q.hall ?? null, + user: q.user ?? null, + userHall: q.userHall ?? null, + userHallOwner: q.userHallOwner ?? null, + fav: q.fav === 'true', + random: q.random === '1' || req.cookies?.random_mode === '1', + strict: q.strict === '1' || q.strict === 'true' || !!req.session?.strict_mode, + session: !!req.session, + user_id: req.session?.id ?? null, + exclude: req.session?.excluded_tags ?? [], + minXdScore: req.session?.min_xd_score ?? 0, + newer: q.newer ?? null, + tagger: q.tagger ?? null + }); + + if (!data.success) { + return json(res, { success: false, items: [], pagination: null, total: 0, link: null }); + } + + return json(res, { + success: true, + items: data.items, + pagination: data.pagination, + total: data.total, + link: data.link + }); + } catch (e) { + console.error('[API v2] /items error:', e); + return json(res, { success: false, error: 'Internal error' }, 500); + } + }); + + // ── GET /api/v2/items/:id ───────────────────────────────────────────────── + router.get(/^\/api\/v2\/items\/(?\d+)$/, async (req, res) => { + const itemid = req.params.itemid; + const q = req.url.qs ?? {}; + const ratingsRaw = req.cookies?.ratings ?? q.ratings ?? null; + const ratingsArr = ratingsRaw + ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) + : null; + + const mode = q.mode !== undefined ? +q.mode : req.mode; + + try { + const data = await f0cklib.getf0ck({ + itemid, + mode, + ratings: ratingsArr, + session: !!req.session, + user_id: req.session?.id ?? null, + user: q.user ?? null, + tag: q.tag ?? null, + hall: q.hall ?? null, + userHall: q.userHall ?? null, + userHallOwner: q.userHallOwner ?? null, + mime: q.mime ?? (req.cookies?.mime ?? null), + fav: q.fav === 'true', + random: q.random === '1', + strict: q.strict === '1' || !!req.session?.strict_mode, + exclude: req.session?.excluded_tags ?? [], + lang: req.lang ?? 'en' + }); + + if (!data.success) { + return json(res, { success: false, item: null, message: data.message }, 404); + } + + // Precompute hall display data (mirrors existing index route logic) + if (data.item?.halls?.length) { + const currentHallSlug = data.tmp?.hall + ? (typeof data.tmp.hall === 'object' ? data.tmp.hall.slug : data.tmp.hall) + : null; + data.item.primaryHall = data.item.halls.find(h => h.slug === currentHallSlug) || data.item.halls[0]; + data.item.otherHalls = data.item.halls.filter(h => h.slug !== data.item.primaryHall.slug); + } else if (data.item) { + data.item.primaryHall = null; + data.item.otherHalls = []; + } + + // Subscription status + const isSubscribed = req.session + ? await f0cklib.getSubscriptionStatus(req.session.id, itemid).catch(() => false) + : false; + + // xD Score + if (req.session || !cfg.main.hide_comments_from_public) { + const commentsForScore = await f0cklib.getComments(itemid, 'old', false); + const xdScore = f0cklib.computeXdScore(commentsForScore); + const xdMeta = f0cklib.xdScoreMeta(xdScore); + data.item.xd_score = xdScore; + data.item.xd_tier = xdMeta.tier; + data.item.xd_label = xdMeta.label; + } else { + data.item.xd_score = 0; + data.item.xd_tier = 0; + data.item.xd_label = ''; + } + + // Notify read + if (req.session?.id) { + f0cklib.markNotificationsRead(req.session.id, itemid).catch(() => {}); + } + + return json(res, { + success: true, + item: data.item, + pagination: data.pagination, + link: data.link, + isSubscribed + }); + } catch (e) { + console.error('[API v2] /items/:id error:', e); + return json(res, { success: false, error: 'Internal error' }, 500); + } + }); + + // ── GET /api/v2/random ──────────────────────────────────────────────────── + router.get('/api/v2/random', async (req, res) => { + const ratingsRaw = req.cookies?.ratings ?? null; + const ratingsArr = ratingsRaw + ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) + : null; + + try { + const data = await f0cklib.getRandom({ + mode: req.mode, + ratings: ratingsArr, + session: !!req.session, + mime: req.cookies?.mime ?? null + }); + + if (!data.success) { + return json(res, { success: false, message: data.message }, 404); + } + + const url = encodeURI(`${data.link.main}${data.link.path ?? ''}${data.itemid}${data.link.suffix ?? ''}`); + return json(res, { success: true, id: data.itemid, url }); + } catch (e) { + console.error('[API v2] /random error:', e); + return json(res, { success: false, error: 'Internal error' }, 500); + } + }); + + return router; +}; diff --git a/src/index.mjs b/src/index.mjs index 87b405c..254d2b4 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -614,7 +614,9 @@ process.on('uncaughtException', err => { let _cachedRow = _scGet(_sessionHash); let user; if (_cachedRow) { - user = [_cachedRow]; + // Shallow-clone the cached row so per-request mutations (theme, uploads_remaining, + // pending_count, etc.) don't corrupt the shared cache entry. + user = [{ ..._cachedRow }]; } else { user = await db` select "user".id, "user".login, "user".user, "user".admin, "user".is_moderator, "user".banned, "user".ban_reason, "user".ban_expires, "user".force_password_change, "user_sessions".id as sess_id, "user_sessions".csrf_token, "user_options".mode, "user_options".theme, "user_options".fullscreen, "user_options".excluded_tags, "user_options".avatar, "user_options".avatar_file, "user_options".show_motd, "user_options".strict_mode, "user_options".show_background, "user_options".use_new_layout, "user_options".username_color, "user_options".font, "user_options".disable_autoplay, "user_options".disable_swiping, "user_options".description, "user_options".display_name, COALESCE("user_options".min_xd_score, 0) as min_xd_score, "user_options".ruffle_volume, "user_options".ruffle_background, "user_options".quote_emojis, "user_options".embed_youtube_in_comments, "user_options".hide_koepfe, "user_options".language, "user_options".use_alternative_infobox, "user_options".use_alternative_steuerung, "user_options".receive_system_notifications, "user_options".receive_user_notifications, "user_options".do_not_disturb, "user_options".comment_display_mode, "user_options".force_comment_display_mode @@ -624,7 +626,8 @@ process.on('uncaughtException', err => { where "user_sessions".session = ${_sessionHash} limit 1 `; - if (user.length > 0) _scSet(_sessionHash, user[0]); + // Store a plain-object clone — keeps the cache entry free of per-request fields. + if (user.length > 0) _scSet(_sessionHash, { ...user[0] }); } if (user.length === 0) {