diff options
33 files changed, 4537 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/.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-* @@ -0,0 +1 @@ +engine-strict=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7d74fe2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# Package Managers +package-lock.json +pnpm-lock.yaml +yarn.lock +bun.lock +bun.lockb + +# Miscellaneous +/static/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8103a0b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ], + "tailwindStylesheet": "./src/app.css" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..75842c4 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# 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 in the current directory +npx sv create + +# create a new project in my-app +npx sv create my-app +``` + +## 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/deno.json b/deno.json new file mode 100644 index 0000000..e4ac711 --- /dev/null +++ b/deno.json @@ -0,0 +1,21 @@ +{ + "version": "0.0.1", + "tasks": { + "dev": "deno run --allow-net --allow-read --allow-write=. --deny-read=/proc/version --allow-env --allow-run --allow-sys=homedir,cpus,networkInterfaces,hostname,osRelease,uid --allow-ffi --unstable-cron --unstable-kv --unstable-bare-node-builtins --unstable-byonm npm:vite", + "build": "deno run --allow-net --allow-read --allow-write=. --deny-read=/proc/version --allow-env --allow-run --allow-sys=homedir,cpus,networkInterfaces,hostname,osRelease,uid --allow-ffi --unstable-cron --unstable-kv --unstable-bare-node-builtins --unstable-byonm npm:vite build", + "preview": "deno run --allow-net --allow-read=. --deny-read=/proc/version --allow-env --allow-sys=homedir,cpus,networkInterfaces,hostname,osRelease,uid --allow-ffi --unstable-cron --unstable-kv --unstable-bare-node-builtins --unstable-byonm npm:vite preview", + "serve": "deno run --allow-net --allow-read=. --deny-read=/proc/version main.ts" + }, + "importMap": "./import_map.json", + "license": "AGPL-3.0-or-later", + "fmt": { + "singleQuote": true + }, + "compilerOptions": { + "checkJs": true, + "strict": true + }, + "unstable": [ + "sloppy-imports" + ] +}
\ No newline at end of file diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..df9d81e --- /dev/null +++ b/deno.lock @@ -0,0 +1,1036 @@ +{ + "version": "5", + "specifiers": { + "jsr:@memdmp/keyframegen@*": "0.2.1", + "jsr:@memdmp/timelinecalc@0.1": "0.1.2", + "npm:@sveltejs/adapter-static@3.0.8": "3.0.8_@sveltejs+kit@2.19.0__@sveltejs+vite-plugin-svelte@5.0.3___svelte@5.23.0____acorn@8.15.0___vite@6.2.1__svelte@5.23.0___acorn@8.15.0__vite@6.2.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.23.0___acorn@8.15.0__vite@6.2.1_svelte@5.23.0__acorn@8.15.0_vite@6.2.1", + "npm:@sveltejs/kit@2.19.0": "2.19.0_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.23.0___acorn@8.15.0__vite@6.2.1_svelte@5.23.0__acorn@8.15.0_vite@6.2.1", + "npm:@sveltejs/vite-plugin-svelte@5.0.3": "5.0.3_svelte@5.23.0__acorn@8.15.0_vite@6.2.1", + "npm:@tailwindcss/vite@*": "4.1.11_vite@6.2.1", + "npm:@tailwindcss/vite@4.1.11": "4.1.11_vite@6.2.1", + "npm:esbuild@0.25.1": "0.25.1", + "npm:prettier-plugin-svelte@3.3.3": "3.3.3_prettier@3.5.3_svelte@5.23.0__acorn@8.15.0", + "npm:prettier@3.5.3": "3.5.3", + "npm:svelte-check@4.1.5": "4.1.5_svelte@5.23.0__acorn@8.15.0_typescript@5.8.2", + "npm:svelte@5.23.0": "5.23.0_acorn@8.15.0", + "npm:tailwindcss@4.0.13": "4.0.13", + "npm:typescript@5.8.2": "5.8.2", + "npm:vite@6.2.1": "6.2.1" + }, + "jsr": { + "@memdmp/keyframegen@0.2.1": { + "integrity": "d0a49ed597422507acb1429f36bec463861b7b48846b7a89564a5b553aa368b7", + "dependencies": [ + "jsr:@memdmp/timelinecalc" + ] + }, + "@memdmp/timelinecalc@0.1.2": { + "integrity": "edd60ee8d1bbf42d8dad10bbea3a7f7c4e72481bdbc15a1d32e05065fdf3af39" + } + }, + "npm": { + "@ampproject/remapping@2.3.0": { + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@emnapi/core@1.4.5": { + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dependencies": [ + "@emnapi/wasi-threads", + "tslib" + ] + }, + "@emnapi/runtime@1.4.5": { + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dependencies": [ + "tslib" + ] + }, + "@emnapi/wasi-threads@1.0.4": { + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dependencies": [ + "tslib" + ] + }, + "@esbuild/aix-ppc64@0.25.1": { + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/aix-ppc64@0.25.8": { + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.25.1": { + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm64@0.25.8": { + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.25.1": { + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-arm@0.25.8": { + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.25.1": { + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/android-x64@0.25.8": { + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.25.1": { + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-arm64@0.25.8": { + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.25.1": { + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/darwin-x64@0.25.8": { + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.25.1": { + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-arm64@0.25.8": { + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.25.1": { + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-x64@0.25.8": { + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.25.1": { + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm64@0.25.8": { + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.25.1": { + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-arm@0.25.8": { + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.25.1": { + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-ia32@0.25.8": { + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.25.1": { + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-loong64@0.25.8": { + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.25.1": { + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-mips64el@0.25.8": { + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.25.1": { + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-ppc64@0.25.8": { + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.25.1": { + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-riscv64@0.25.8": { + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.25.1": { + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-s390x@0.25.8": { + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.25.1": { + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/linux-x64@0.25.8": { + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.25.1": { + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-arm64@0.25.8": { + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.25.1": { + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-x64@0.25.8": { + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.25.1": { + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-arm64@0.25.8": { + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.25.1": { + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-x64@0.25.8": { + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.25.8": { + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.25.1": { + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/sunos-x64@0.25.8": { + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.25.1": { + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-arm64@0.25.8": { + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.25.1": { + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-ia32@0.25.8": { + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.25.1": { + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@esbuild/win32-x64@0.25.8": { + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@isaacs/fs-minipass@4.0.1": { + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dependencies": [ + "minipass" + ] + }, + "@jridgewell/gen-mapping@0.3.12": { + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/sourcemap-codec@1.5.4": { + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" + }, + "@jridgewell/trace-mapping@0.3.29": { + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, + "@napi-rs/wasm-runtime@0.2.12": { + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dependencies": [ + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util@0.10.0" + ] + }, + "@polka/url@1.0.0-next.29": { + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" + }, + "@rollup/rollup-android-arm-eabi@4.46.2": { + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "os": ["android"], + "cpu": ["arm"] + }, + "@rollup/rollup-android-arm64@4.46.2": { + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-arm64@4.46.2": { + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@rollup/rollup-darwin-x64@4.46.2": { + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@rollup/rollup-freebsd-arm64@4.46.2": { + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@rollup/rollup-freebsd-x64@4.46.2": { + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-arm-gnueabihf@4.46.2": { + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm-musleabihf@4.46.2": { + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@rollup/rollup-linux-arm64-gnu@4.46.2": { + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-arm64-musl@4.46.2": { + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@rollup/rollup-linux-loongarch64-gnu@4.46.2": { + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@rollup/rollup-linux-ppc64-gnu@4.46.2": { + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@rollup/rollup-linux-riscv64-gnu@4.46.2": { + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-riscv64-musl@4.46.2": { + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@rollup/rollup-linux-s390x-gnu@4.46.2": { + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@rollup/rollup-linux-x64-gnu@4.46.2": { + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-linux-x64-musl@4.46.2": { + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@rollup/rollup-win32-arm64-msvc@4.46.2": { + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@rollup/rollup-win32-ia32-msvc@4.46.2": { + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@rollup/rollup-win32-x64-msvc@4.46.2": { + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@sveltejs/acorn-typescript@1.0.5_acorn@8.15.0": { + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dependencies": [ + "acorn" + ] + }, + "@sveltejs/adapter-static@3.0.8_@sveltejs+kit@2.19.0__@sveltejs+vite-plugin-svelte@5.0.3___svelte@5.23.0____acorn@8.15.0___vite@6.2.1__svelte@5.23.0___acorn@8.15.0__vite@6.2.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.23.0___acorn@8.15.0__vite@6.2.1_svelte@5.23.0__acorn@8.15.0_vite@6.2.1": { + "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", + "dependencies": [ + "@sveltejs/kit" + ] + }, + "@sveltejs/kit@2.19.0_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.23.0___acorn@8.15.0__vite@6.2.1_svelte@5.23.0__acorn@8.15.0_vite@6.2.1": { + "integrity": "sha512-UTx28Ad4sYsLU//gqkEo5aFOPFBRT2uXCmXTsURqhurDCvzkVwXruJgBcHDaMiK6RKKpYRteDUaXYqZyGPgCXQ==", + "dependencies": [ + "@sveltejs/vite-plugin-svelte", + "@types/cookie", + "cookie", + "devalue", + "esm-env", + "import-meta-resolve", + "kleur", + "magic-string", + "mrmime", + "sade", + "set-cookie-parser", + "sirv", + "svelte", + "vite" + ], + "bin": true + }, + "@sveltejs/vite-plugin-svelte-inspector@4.0.1_@sveltejs+vite-plugin-svelte@5.0.3__svelte@5.23.0___acorn@8.15.0__vite@6.2.1_svelte@5.23.0__acorn@8.15.0_vite@6.2.1": { + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dependencies": [ + "@sveltejs/vite-plugin-svelte", + "debug", + "svelte", + "vite" + ] + }, + "@sveltejs/vite-plugin-svelte@5.0.3_svelte@5.23.0__acorn@8.15.0_vite@6.2.1": { + "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", + "dependencies": [ + "@sveltejs/vite-plugin-svelte-inspector", + "debug", + "deepmerge", + "kleur", + "magic-string", + "svelte", + "vite", + "vitefu" + ] + }, + "@tailwindcss/node@4.1.11": { + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "dependencies": [ + "@ampproject/remapping", + "enhanced-resolve", + "jiti", + "lightningcss", + "magic-string", + "source-map-js", + "tailwindcss@4.1.11" + ] + }, + "@tailwindcss/oxide-android-arm64@4.1.11": { + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-arm64@4.1.11": { + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-darwin-x64@4.1.11": { + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-freebsd-x64@4.1.11": { + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": { + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": { + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-arm64-musl@4.1.11": { + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-linux-x64-gnu@4.1.11": { + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-linux-x64-musl@4.1.11": { + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide-wasm32-wasi@4.1.11": { + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "dependencies": [ + "@emnapi/core", + "@emnapi/runtime", + "@emnapi/wasi-threads", + "@napi-rs/wasm-runtime", + "@tybys/wasm-util@0.9.0", + "tslib" + ], + "cpu": ["wasm32"] + }, + "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": { + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@tailwindcss/oxide-win32-x64-msvc@4.1.11": { + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@tailwindcss/oxide@4.1.11": { + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "dependencies": [ + "detect-libc", + "tar" + ], + "optionalDependencies": [ + "@tailwindcss/oxide-android-arm64", + "@tailwindcss/oxide-darwin-arm64", + "@tailwindcss/oxide-darwin-x64", + "@tailwindcss/oxide-freebsd-x64", + "@tailwindcss/oxide-linux-arm-gnueabihf", + "@tailwindcss/oxide-linux-arm64-gnu", + "@tailwindcss/oxide-linux-arm64-musl", + "@tailwindcss/oxide-linux-x64-gnu", + "@tailwindcss/oxide-linux-x64-musl", + "@tailwindcss/oxide-wasm32-wasi", + "@tailwindcss/oxide-win32-arm64-msvc", + "@tailwindcss/oxide-win32-x64-msvc" + ], + "scripts": true + }, + "@tailwindcss/vite@4.1.11_vite@6.2.1": { + "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", + "dependencies": [ + "@tailwindcss/node", + "@tailwindcss/oxide", + "tailwindcss@4.1.11", + "vite" + ] + }, + "@tybys/wasm-util@0.10.0": { + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "dependencies": [ + "tslib" + ] + }, + "@tybys/wasm-util@0.9.0": { + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dependencies": [ + "tslib" + ] + }, + "@types/cookie@0.6.0": { + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "acorn@8.15.0": { + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": true + }, + "aria-query@5.3.2": { + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" + }, + "axobject-query@4.1.0": { + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" + }, + "chokidar@4.0.3": { + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dependencies": [ + "readdirp" + ] + }, + "chownr@3.0.0": { + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" + }, + "clsx@2.1.1": { + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, + "cookie@0.6.0": { + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + }, + "debug@4.4.1": { + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": [ + "ms" + ] + }, + "deepmerge@4.3.1": { + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "detect-libc@2.0.4": { + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==" + }, + "devalue@5.1.1": { + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==" + }, + "enhanced-resolve@5.18.2": { + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "dependencies": [ + "graceful-fs", + "tapable" + ] + }, + "esbuild@0.25.1": { + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "optionalDependencies": [ + "@esbuild/aix-ppc64@0.25.1", + "@esbuild/android-arm@0.25.1", + "@esbuild/android-arm64@0.25.1", + "@esbuild/android-x64@0.25.1", + "@esbuild/darwin-arm64@0.25.1", + "@esbuild/darwin-x64@0.25.1", + "@esbuild/freebsd-arm64@0.25.1", + "@esbuild/freebsd-x64@0.25.1", + "@esbuild/linux-arm@0.25.1", + "@esbuild/linux-arm64@0.25.1", + "@esbuild/linux-ia32@0.25.1", + "@esbuild/linux-loong64@0.25.1", + "@esbuild/linux-mips64el@0.25.1", + "@esbuild/linux-ppc64@0.25.1", + "@esbuild/linux-riscv64@0.25.1", + "@esbuild/linux-s390x@0.25.1", + "@esbuild/linux-x64@0.25.1", + "@esbuild/netbsd-arm64@0.25.1", + "@esbuild/netbsd-x64@0.25.1", + "@esbuild/openbsd-arm64@0.25.1", + "@esbuild/openbsd-x64@0.25.1", + "@esbuild/sunos-x64@0.25.1", + "@esbuild/win32-arm64@0.25.1", + "@esbuild/win32-ia32@0.25.1", + "@esbuild/win32-x64@0.25.1" + ], + "scripts": true, + "bin": true + }, + "esm-env@1.2.2": { + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" + }, + "esrap@1.4.9": { + "integrity": "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "fdir@6.4.6": { + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==" + }, + "fsevents@2.3.3": { + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "os": ["darwin"], + "scripts": true + }, + "graceful-fs@4.2.11": { + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "import-meta-resolve@4.1.0": { + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==" + }, + "is-reference@3.0.3": { + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dependencies": [ + "@types/estree" + ] + }, + "jiti@2.5.1": { + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "bin": true + }, + "kleur@4.1.5": { + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" + }, + "lightningcss-darwin-arm64@1.30.1": { + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "lightningcss-darwin-x64@1.30.1": { + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "lightningcss-freebsd-x64@1.30.1": { + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "lightningcss-linux-arm-gnueabihf@1.30.1": { + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "os": ["linux"], + "cpu": ["arm"] + }, + "lightningcss-linux-arm64-gnu@1.30.1": { + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-arm64-musl@1.30.1": { + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "lightningcss-linux-x64-gnu@1.30.1": { + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-linux-x64-musl@1.30.1": { + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "lightningcss-win32-arm64-msvc@1.30.1": { + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "lightningcss-win32-x64-msvc@1.30.1": { + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "os": ["win32"], + "cpu": ["x64"] + }, + "lightningcss@1.30.1": { + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dependencies": [ + "detect-libc" + ], + "optionalDependencies": [ + "lightningcss-darwin-arm64", + "lightningcss-darwin-x64", + "lightningcss-freebsd-x64", + "lightningcss-linux-arm-gnueabihf", + "lightningcss-linux-arm64-gnu", + "lightningcss-linux-arm64-musl", + "lightningcss-linux-x64-gnu", + "lightningcss-linux-x64-musl", + "lightningcss-win32-arm64-msvc", + "lightningcss-win32-x64-msvc" + ] + }, + "locate-character@3.0.0": { + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" + }, + "magic-string@0.30.17": { + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "minipass@7.1.2": { + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "minizlib@3.0.2": { + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dependencies": [ + "minipass" + ] + }, + "mkdirp@3.0.1": { + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "bin": true + }, + "mri@1.2.0": { + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, + "mrmime@2.0.1": { + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "nanoid@3.3.11": { + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "bin": true + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "postcss@8.5.6": { + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dependencies": [ + "nanoid", + "picocolors", + "source-map-js" + ] + }, + "prettier-plugin-svelte@3.3.3_prettier@3.5.3_svelte@5.23.0__acorn@8.15.0": { + "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", + "dependencies": [ + "prettier", + "svelte" + ] + }, + "prettier@3.5.3": { + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "bin": true + }, + "readdirp@4.1.2": { + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" + }, + "rollup@4.46.2": { + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dependencies": [ + "@types/estree" + ], + "optionalDependencies": [ + "@rollup/rollup-android-arm-eabi", + "@rollup/rollup-android-arm64", + "@rollup/rollup-darwin-arm64", + "@rollup/rollup-darwin-x64", + "@rollup/rollup-freebsd-arm64", + "@rollup/rollup-freebsd-x64", + "@rollup/rollup-linux-arm-gnueabihf", + "@rollup/rollup-linux-arm-musleabihf", + "@rollup/rollup-linux-arm64-gnu", + "@rollup/rollup-linux-arm64-musl", + "@rollup/rollup-linux-loongarch64-gnu", + "@rollup/rollup-linux-ppc64-gnu", + "@rollup/rollup-linux-riscv64-gnu", + "@rollup/rollup-linux-riscv64-musl", + "@rollup/rollup-linux-s390x-gnu", + "@rollup/rollup-linux-x64-gnu", + "@rollup/rollup-linux-x64-musl", + "@rollup/rollup-win32-arm64-msvc", + "@rollup/rollup-win32-ia32-msvc", + "@rollup/rollup-win32-x64-msvc", + "fsevents" + ], + "bin": true + }, + "sade@1.8.1": { + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": [ + "mri" + ] + }, + "set-cookie-parser@2.7.1": { + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, + "sirv@3.0.1": { + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dependencies": [ + "@polka/url", + "mrmime", + "totalist" + ] + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "svelte-check@4.1.5_svelte@5.23.0__acorn@8.15.0_typescript@5.8.2": { + "integrity": "sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==", + "dependencies": [ + "@jridgewell/trace-mapping", + "chokidar", + "fdir", + "picocolors", + "sade", + "svelte", + "typescript" + ], + "bin": true + }, + "svelte@5.23.0_acorn@8.15.0": { + "integrity": "sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==", + "dependencies": [ + "@ampproject/remapping", + "@jridgewell/sourcemap-codec", + "@sveltejs/acorn-typescript", + "@types/estree", + "acorn", + "aria-query", + "axobject-query", + "clsx", + "esm-env", + "esrap", + "is-reference", + "locate-character", + "magic-string", + "zimmerframe" + ] + }, + "tailwindcss@4.0.13": { + "integrity": "sha512-gbvFrB0fOsTv/OugXWi2PtflJ4S6/ctu6Mmn3bCftmLY/6xRsQVEJPgIIpABwpZ52DpONkCA3bEj5b54MHxF2Q==" + }, + "tailwindcss@4.1.11": { + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" + }, + "tapable@2.2.2": { + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==" + }, + "tar@7.4.3": { + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dependencies": [ + "@isaacs/fs-minipass", + "chownr", + "minipass", + "minizlib", + "mkdirp", + "yallist" + ] + }, + "totalist@3.0.1": { + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" + }, + "tslib@2.8.1": { + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "typescript@5.8.2": { + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "bin": true + }, + "vite@6.2.1": { + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "dependencies": [ + "esbuild", + "postcss", + "rollup" + ], + "optionalDependencies": [ + "fsevents" + ], + "bin": true + }, + "vitefu@1.1.1_vite@6.2.1": { + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dependencies": [ + "vite" + ], + "optionalPeers": [ + "vite" + ] + }, + "yallist@5.0.0": { + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" + }, + "zimmerframe@1.1.2": { + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==" + } + }, + "workspace": { + "dependencies": [ + "npm:@tailwindcss/vite@*" + ], + "packageJson": { + "dependencies": [ + "npm:@sveltejs/adapter-static@3.0.8", + "npm:@sveltejs/kit@2.19.0", + "npm:@sveltejs/vite-plugin-svelte@5.0.3", + "npm:@tailwindcss/vite@4.1.11", + "npm:esbuild@0.25.1", + "npm:prettier-plugin-svelte@3.3.3", + "npm:prettier@3.5.3", + "npm:svelte-check@4.1.5", + "npm:svelte@5.23.0", + "npm:tailwindcss@4.0.13", + "npm:typescript@5.8.2", + "npm:vite@6.2.1" + ] + } + } +} diff --git a/import_map.json b/import_map.json new file mode 100644 index 0000000..5a5ed87 --- /dev/null +++ b/import_map.json @@ -0,0 +1,9 @@ +{ + "imports": { + "$": "./src", + "$/": "./src/", + "$lib": "./src/lib", + "$lib/": "./src/lib/", + "@tailwindcss/vite": "npm:@tailwindcss/vite" + } +}
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a6c9f48 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "videotool", + "private": true, + "version": "0.0.1", + "type": "module", + "devDependencies": { + "@sveltejs/adapter-static": "3.0.8", + "@sveltejs/kit": "2.19.0", + "@sveltejs/vite-plugin-svelte": "5.0.3", + "@tailwindcss/vite": "4.1.11", + "esbuild": "0.25.1", + "prettier": "3.5.3", + "prettier-plugin-svelte": "3.3.3", + "svelte": "5.23.0", + "svelte-check": "4.1.5", + "tailwindcss": "4.0.13", + "typescript": "5.8.2", + "vite": "6.2.1" + }, + "dependencies": { + "@ffmpeg/ffmpeg": "^0.12.15", + "@ffmpeg/util": "^0.12.2" + } +}
\ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..eba1dcc --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1359 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ffmpeg/ffmpeg': + specifier: ^0.12.15 + version: 0.12.15 + '@ffmpeg/util': + specifier: ^0.12.2 + version: 0.12.2 + devDependencies: + '@sveltejs/adapter-static': + specifier: 3.0.8 + version: 3.0.8(@sveltejs/kit@2.19.0(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1))) + '@sveltejs/kit': + specifier: 2.19.0 + version: 2.19.0(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': + specifier: 5.0.3 + version: 5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + '@tailwindcss/vite': + specifier: 4.1.11 + version: 4.1.11(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + esbuild: + specifier: 0.25.1 + version: 0.25.1 + prettier: + specifier: 3.5.3 + version: 3.5.3 + prettier-plugin-svelte: + specifier: 3.3.3 + version: 3.3.3(prettier@3.5.3)(svelte@5.23.0) + svelte: + specifier: 5.23.0 + version: 5.23.0 + svelte-check: + specifier: 4.1.5 + version: 4.1.5(svelte@5.23.0)(typescript@5.8.2) + tailwindcss: + specifier: 4.0.13 + version: 4.0.13 + typescript: + specifier: 5.8.2 + version: 5.8.2 + vite: + specifier: 6.2.1 + version: 6.2.1(jiti@2.4.2)(lightningcss@1.30.1) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@esbuild/aix-ppc64@0.25.1': + resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.1': + resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.1': + resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.1': + resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.1': + resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.1': + resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.1': + resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.1': + resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.1': + resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.1': + resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.1': + resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.1': + resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.1': + resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.1': + resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.1': + resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.1': + resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.1': + resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.1': + resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.1': + resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.1': + resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.1': + resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.1': + resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.1': + resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.1': + resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.1': + resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@ffmpeg/ffmpeg@0.12.15': + resolution: {integrity: sha512-1C8Obr4GsN3xw+/1Ww6PFM84wSQAGsdoTuTWPOj2OizsRDLT4CXTaVjPhkw6ARyDus1B9X/L2LiXHqYYsGnRFw==} + engines: {node: '>=18.x'} + + '@ffmpeg/types@0.12.4': + resolution: {integrity: sha512-k9vJQNBGTxE5AhYDtOYR5rO5fKsspbg51gbcwtbkw2lCdoIILzklulcjJfIDwrtn7XhDeF2M+THwJ2FGrLeV6A==} + engines: {node: '>=16.x'} + + '@ffmpeg/util@0.12.2': + resolution: {integrity: sha512-ouyoW+4JB7WxjeZ2y6KpRvB+dLp7Cp4ro8z0HIVpZVCM7AwFlHa0c4R8Y/a4M3wMqATpYKhC7lSFHQ0T11MEDw==} + engines: {node: '>=18.x'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + + '@rollup/rollup-android-arm-eabi@4.35.0': + resolution: {integrity: sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.35.0': + resolution: {integrity: sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.35.0': + resolution: {integrity: sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.35.0': + resolution: {integrity: sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.35.0': + resolution: {integrity: sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.35.0': + resolution: {integrity: sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.35.0': + resolution: {integrity: sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.35.0': + resolution: {integrity: sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.35.0': + resolution: {integrity: sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.35.0': + resolution: {integrity: sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.35.0': + resolution: {integrity: sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': + resolution: {integrity: sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.35.0': + resolution: {integrity: sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.35.0': + resolution: {integrity: sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.35.0': + resolution: {integrity: sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.35.0': + resolution: {integrity: sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.35.0': + resolution: {integrity: sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.35.0': + resolution: {integrity: sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.35.0': + resolution: {integrity: sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==} + cpu: [x64] + os: [win32] + + '@sveltejs/acorn-typescript@1.0.5': + resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-static@3.0.8': + resolution: {integrity: sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.19.0': + resolution: {integrity: sha512-UTx28Ad4sYsLU//gqkEo5aFOPFBRT2uXCmXTsURqhurDCvzkVwXruJgBcHDaMiK6RKKpYRteDUaXYqZyGPgCXQ==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1': + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + + '@sveltejs/vite-plugin-svelte@5.0.3': + resolution: {integrity: sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + + '@tailwindcss/node@4.1.11': + resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} + + '@tailwindcss/oxide-android-arm64@4.1.11': + resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.11': + resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.11': + resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.11': + resolution: {integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + devalue@5.1.1: + resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + + esbuild@0.25.1: + resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} + engines: {node: '>=18'} + hasBin: true + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + esrap@1.4.5: + resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==} + + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.9: + resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prettier-plugin-svelte@3.3.3: + resolution: {integrity: sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + rollup@4.35.0: + resolution: {integrity: sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + svelte-check@4.1.5: + resolution: {integrity: sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte@5.23.0: + resolution: {integrity: sha512-v0lL3NuKontiCxholEiAXCB+BYbndlKbwlDMK0DS86WgGELMJSpyqCSbJeMEMBDwOglnS7Ar2Rq0wwa/z2L8Vg==} + engines: {node: '>=18'} + + tailwindcss@4.0.13: + resolution: {integrity: sha512-gbvFrB0fOsTv/OugXWi2PtflJ4S6/ctu6Mmn3bCftmLY/6xRsQVEJPgIIpABwpZ52DpONkCA3bEj5b54MHxF2Q==} + + tailwindcss@4.1.11: + resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + vite@6.2.1: + resolution: {integrity: sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.0.6: + resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + vite: + optional: true + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@esbuild/aix-ppc64@0.25.1': + optional: true + + '@esbuild/android-arm64@0.25.1': + optional: true + + '@esbuild/android-arm@0.25.1': + optional: true + + '@esbuild/android-x64@0.25.1': + optional: true + + '@esbuild/darwin-arm64@0.25.1': + optional: true + + '@esbuild/darwin-x64@0.25.1': + optional: true + + '@esbuild/freebsd-arm64@0.25.1': + optional: true + + '@esbuild/freebsd-x64@0.25.1': + optional: true + + '@esbuild/linux-arm64@0.25.1': + optional: true + + '@esbuild/linux-arm@0.25.1': + optional: true + + '@esbuild/linux-ia32@0.25.1': + optional: true + + '@esbuild/linux-loong64@0.25.1': + optional: true + + '@esbuild/linux-mips64el@0.25.1': + optional: true + + '@esbuild/linux-ppc64@0.25.1': + optional: true + + '@esbuild/linux-riscv64@0.25.1': + optional: true + + '@esbuild/linux-s390x@0.25.1': + optional: true + + '@esbuild/linux-x64@0.25.1': + optional: true + + '@esbuild/netbsd-arm64@0.25.1': + optional: true + + '@esbuild/netbsd-x64@0.25.1': + optional: true + + '@esbuild/openbsd-arm64@0.25.1': + optional: true + + '@esbuild/openbsd-x64@0.25.1': + optional: true + + '@esbuild/sunos-x64@0.25.1': + optional: true + + '@esbuild/win32-arm64@0.25.1': + optional: true + + '@esbuild/win32-ia32@0.25.1': + optional: true + + '@esbuild/win32-x64@0.25.1': + optional: true + + '@ffmpeg/ffmpeg@0.12.15': + dependencies: + '@ffmpeg/types': 0.12.4 + + '@ffmpeg/types@0.12.4': {} + + '@ffmpeg/util@0.12.2': {} + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@polka/url@1.0.0-next.28': {} + + '@rollup/rollup-android-arm-eabi@4.35.0': + optional: true + + '@rollup/rollup-android-arm64@4.35.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.35.0': + optional: true + + '@rollup/rollup-darwin-x64@4.35.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.35.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.35.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.35.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.35.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.35.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.35.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.35.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.35.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.35.0': + optional: true + + '@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)': + dependencies: + acorn: 8.14.1 + + '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.19.0(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))': + dependencies: + '@sveltejs/kit': 2.19.0(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + + '@sveltejs/kit@2.19.0(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + '@types/cookie': 0.6.0 + cookie: 0.6.0 + devalue: 5.1.1 + esm-env: 1.2.2 + import-meta-resolve: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.17 + mrmime: 2.0.1 + sade: 1.8.1 + set-cookie-parser: 2.7.1 + sirv: 3.0.1 + svelte: 5.23.0 + vite: 6.2.1(jiti@2.4.2)(lightningcss@1.30.1) + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + debug: 4.4.0 + svelte: 5.23.0 + vite: 6.2.1(jiti@2.4.2)(lightningcss@1.30.1) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.23.0)(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + debug: 4.4.0 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.17 + svelte: 5.23.0 + vite: 6.2.1(jiti@2.4.2)(lightningcss@1.30.1) + vitefu: 1.0.6(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)) + transitivePeerDependencies: + - supports-color + + '@tailwindcss/node@4.1.11': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.11 + + '@tailwindcss/oxide-android-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide@4.1.11': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-x64': 4.1.11 + '@tailwindcss/oxide-freebsd-x64': 4.1.11 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-x64-musl': 4.1.11 + '@tailwindcss/oxide-wasm32-wasi': 4.1.11 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + + '@tailwindcss/vite@4.1.11(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1))': + dependencies: + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 + tailwindcss: 4.1.11 + vite: 6.2.1(jiti@2.4.2)(lightningcss@1.30.1) + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.6': {} + + acorn@8.14.1: {} + + aria-query@5.3.2: {} + + axobject-query@4.1.0: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chownr@3.0.0: {} + + clsx@2.1.1: {} + + cookie@0.6.0: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deepmerge@4.3.1: {} + + detect-libc@2.0.4: {} + + devalue@5.1.1: {} + + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + esbuild@0.25.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.1 + '@esbuild/android-arm': 0.25.1 + '@esbuild/android-arm64': 0.25.1 + '@esbuild/android-x64': 0.25.1 + '@esbuild/darwin-arm64': 0.25.1 + '@esbuild/darwin-x64': 0.25.1 + '@esbuild/freebsd-arm64': 0.25.1 + '@esbuild/freebsd-x64': 0.25.1 + '@esbuild/linux-arm': 0.25.1 + '@esbuild/linux-arm64': 0.25.1 + '@esbuild/linux-ia32': 0.25.1 + '@esbuild/linux-loong64': 0.25.1 + '@esbuild/linux-mips64el': 0.25.1 + '@esbuild/linux-ppc64': 0.25.1 + '@esbuild/linux-riscv64': 0.25.1 + '@esbuild/linux-s390x': 0.25.1 + '@esbuild/linux-x64': 0.25.1 + '@esbuild/netbsd-arm64': 0.25.1 + '@esbuild/netbsd-x64': 0.25.1 + '@esbuild/openbsd-arm64': 0.25.1 + '@esbuild/openbsd-x64': 0.25.1 + '@esbuild/sunos-x64': 0.25.1 + '@esbuild/win32-arm64': 0.25.1 + '@esbuild/win32-ia32': 0.25.1 + '@esbuild/win32-x64': 0.25.1 + + esm-env@1.2.2: {} + + esrap@1.4.5: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + fdir@6.4.3: {} + + fsevents@2.3.3: + optional: true + + graceful-fs@4.2.11: {} + + import-meta-resolve@4.1.0: {} + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + jiti@2.4.2: {} + + kleur@4.1.5: {} + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + locate-character@3.0.0: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + mri@1.2.0: {} + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.9: {} + + picocolors@1.1.1: {} + + postcss@8.5.3: + dependencies: + nanoid: 3.3.9 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.23.0): + dependencies: + prettier: 3.5.3 + svelte: 5.23.0 + + prettier@3.5.3: {} + + readdirp@4.1.2: {} + + rollup@4.35.0: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.35.0 + '@rollup/rollup-android-arm64': 4.35.0 + '@rollup/rollup-darwin-arm64': 4.35.0 + '@rollup/rollup-darwin-x64': 4.35.0 + '@rollup/rollup-freebsd-arm64': 4.35.0 + '@rollup/rollup-freebsd-x64': 4.35.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.35.0 + '@rollup/rollup-linux-arm-musleabihf': 4.35.0 + '@rollup/rollup-linux-arm64-gnu': 4.35.0 + '@rollup/rollup-linux-arm64-musl': 4.35.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.35.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.35.0 + '@rollup/rollup-linux-riscv64-gnu': 4.35.0 + '@rollup/rollup-linux-s390x-gnu': 4.35.0 + '@rollup/rollup-linux-x64-gnu': 4.35.0 + '@rollup/rollup-linux-x64-musl': 4.35.0 + '@rollup/rollup-win32-arm64-msvc': 4.35.0 + '@rollup/rollup-win32-ia32-msvc': 4.35.0 + '@rollup/rollup-win32-x64-msvc': 4.35.0 + fsevents: 2.3.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + set-cookie-parser@2.7.1: {} + + sirv@3.0.1: + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + svelte-check@4.1.5(svelte@5.23.0)(typescript@5.8.2): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + chokidar: 4.0.3 + fdir: 6.4.3 + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.23.0 + typescript: 5.8.2 + transitivePeerDependencies: + - picomatch + + svelte@5.23.0: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) + '@types/estree': 1.0.6 + acorn: 8.14.1 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 1.4.5 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.17 + zimmerframe: 1.1.2 + + tailwindcss@4.0.13: {} + + tailwindcss@4.1.11: {} + + tapable@2.2.1: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + totalist@3.0.1: {} + + typescript@5.8.2: {} + + vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1): + dependencies: + esbuild: 0.25.1 + postcss: 8.5.3 + rollup: 4.35.0 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.30.1 + + vitefu@1.0.6(vite@6.2.1(jiti@2.4.2)(lightningcss@1.30.1)): + optionalDependencies: + vite: 6.2.1(jiti@2.4.2)(lightningcss@1.30.1) + + yallist@5.0.0: {} + + zimmerframe@1.1.2: {} diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..d4b5078 --- /dev/null +++ b/src/app.css @@ -0,0 +1 @@ +@import 'tailwindcss'; diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..d1e03cb --- /dev/null +++ b/src/app.html @@ -0,0 +1,14 @@ +<!doctype html> +<html lang="en" class='bg-[#0a0a0a] text-white'> + +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + %sveltekit.head% +</head> + +<body data-sveltekit-preload-data="hover"> + <div style="display: contents">%sveltekit.body%</div> +</body> + +</html> diff --git a/src/lib/Player/FrameSlider.svelte b/src/lib/Player/FrameSlider.svelte new file mode 100644 index 0000000..a632aa9 --- /dev/null +++ b/src/lib/Player/FrameSlider.svelte @@ -0,0 +1,67 @@ +<script lang="ts"> + import RangeSlider from '../vendor/svelte-range-slider/range-slider.svelte'; + let { + frame = $bindable(), + frameCount, + playing = $bindable(), + playbackStarted = $bindable() + }: { + frame: number; + frameCount: number; + playing: boolean; + playbackStarted: number; + } = $props(); +</script> + +<div class="w-full flex items-center justify-center pt-2"> + <div class="p-2 pr-1"> + <button + onclick={() => { + playing = !playing; + }} + aria-label={playing ? 'pause' : 'play'} + class="flex items-center justify-center" + ><svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="1.5" + stroke="currentColor" + class="size-6" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + d={!playing + ? 'M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z' + : 'M15.75 5.25v13.5m-7.5-13.5v13.5'} + /> + </svg></button + > + </div> + <div class="flex-1"> + <RangeSlider bind:value={frame} min={0} max={frameCount} /> + </div> + <div class="label p-2 pl-1"> + <input + type="number" + bind:value={frame} + class="w-16 appearance-none text-right" + style="-moz-appearance:textfield;" + max={frameCount} + onkeypress={(e) => { + e.stopPropagation(); + }} + onkeydown={(e) => { + e.stopPropagation(); + }} + /> + of {frameCount} + <!-- <input + type="number" + bind:value={frameCount} + class="w-8 appearance-none" + style="-moz-appearance:textfield;" + /> --> + </div> +</div> diff --git a/src/lib/Player/Keybinds.svelte b/src/lib/Player/Keybinds.svelte new file mode 100644 index 0000000..756a865 --- /dev/null +++ b/src/lib/Player/Keybinds.svelte @@ -0,0 +1,47 @@ +<script lang="ts"> + let { + frame = $bindable(), + frameCount, + fps, + playing = $bindable(), + playbackStarted = $bindable() + }: { + frame: number; + frameCount: number; + fps: number | undefined; + playing: boolean; + playbackStarted: number; + } = $props(); +</script> + +<svelte:window + onkeypress={(e) => { + switch (e.key) { + case ' ': + e.preventDefault(); + playing = !playing; + if (playing) playbackStarted = performance.now(); + break; + + // default: + // if (dev) console.debug('Keypress:', e.key); + // break; + } + }} + onkeydown={(e) => { + switch (e.key) { + case 'ArrowLeft': + e.preventDefault(); + frame = Math.max(frame - (e.ctrlKey ? (fps ?? 60) : 1), 0); + break; + case 'ArrowRight': + e.preventDefault(); + frame = Math.min(frame + (e.ctrlKey ? (fps ?? 60) : 1), frameCount); + break; + + // default: + // if (dev) console.debug('Keydown:', e.key); + // break; + } + }} +/> diff --git a/src/lib/Player/Player.svelte b/src/lib/Player/Player.svelte new file mode 100644 index 0000000..f6df121 --- /dev/null +++ b/src/lib/Player/Player.svelte @@ -0,0 +1,161 @@ +<script lang="ts"> + import FrameSlider from './FrameSlider.svelte'; + import { type Video, type VideoConstructor } from '$/lib/Player/Video'; + import Keybinds from './Keybinds.svelte'; + import { onMount } from 'svelte'; + + let frame = $state(0); + let canvas = $state(null as HTMLCanvasElement | null); + let audio = $state(null as HTMLAudioElement | null); + let lastCanvas = null as typeof canvas; + let video = $state(undefined as Video | undefined); + let frameCount = $state(0); + let playing = $state(false); + let playbackStarted = $state(0); + let playbackFrameOffset = 0; + let renderPromise: Promise<void> | void = void 0; + let renderId = 0; + let audioSource = $state(null as null | string); + let { + Video: VideoImplementation + }: { + Video: VideoConstructor; + } = $props(); + const newCanvas = async (canvas: HTMLCanvasElement, videoImplementation: VideoConstructor) => { + if (video) video.cleanup(); + lastCanvas = canvas; + video = new VideoImplementation(canvas); + const audioSourcePromise = (async () => { + if (video?.audioUrl?.[1]) { + audioSource = await fetch(video.audioUrl[1]) + .then(async (v) => { + if (v.status.toString().startsWith('2')) return URL.createObjectURL(await v.blob()); + else throw new Error('non-2xx audio'); + }) + .catch((e) => { + console.warn('Failed to get audio', e); + return video?.audioUrl?.[1] ?? null; + }); + } + })(); + video['_isInit'] = true; + renderPromise = video.init(); + await renderPromise; + video['_isInit'] = false; + frameCount = video.length; + await audioSourcePromise; + }; + const renderPreviewFrame = async (video: Video, frame: number) => { + const ourId = ++renderId; + if (renderPromise) await renderPromise; + if (renderId !== ourId) return; + renderPromise = + video.renderFrame({ + frames: frame, + milliseconds: (frame / video.fps) * 1000, + seconds: frame / video.fps + }) ?? Promise.resolve(); + return renderPromise; + }; + $effect(() => { + if (canvas && canvas !== lastCanvas) newCanvas(canvas, VideoImplementation); + }); + $effect(() => { + if (video) renderPreviewFrame(video, frame); + }); + let playbackLoopId = 0; + const startPlaybackLoop = (id = ++playbackLoopId) => { + if (video && id === playbackLoopId) { + const ms = performance.now() - playbackStarted; + let f = Math.floor((ms / 1000) * video.fps) + playbackFrameOffset; + + if (f > frameCount) { + f = frameCount; + playing = false; + } + frame = f; + renderPreviewFrame(video, frame).then(() => + requestAnimationFrame(() => (playing ? startPlaybackLoop(id) : void 0)) + ); + } + }; + $effect(() => { + if (playing) { + playbackStarted = performance.now(); + playbackFrameOffset = frame; + startPlaybackLoop(); + } + }); + let loadedFrameTimestamp = false; + onMount(() => { + const t = sessionStorage.getItem('timestamp'); + const tI = t ? parseInt(t, 36) : null; + if (tI && !isNaN(tI)) { + frame = tI; + requestAnimationFrame(() => (frame = tI)); + } + loadedFrameTimestamp = true; + }); + // TODO: implement waitin a few seconds before saving + $effect(() => { + if (loadedFrameTimestamp) + try { + sessionStorage.setItem('timestamp', frame.toString(36)); + } catch (_) {} + }); + $effect(() => { + if (audio && video && !playing) { + try { + const f = frame; + audio.currentTime = frame / video.fps; + audio.play(); + (async () => { + const targetTime = (frame + 1) / video.fps; + while (audio.currentTime <= targetTime) { + await new Promise((rs) => requestAnimationFrame(rs)); + if (playing || frame !== f) return; + } + audio.pause(); + audio.currentTime = frame / video.fps; + })(); + } catch (error) { + console.warn(error); + } + } + }); + $effect(() => { + if (audio) { + if (playing) audio.play(); + else audio.pause(); + } + }); +</script> + +<svelte:window + onresize={() => { + if (canvas && video) { + (async () => { + video['_isInit'] = true; + renderPromise = await video.init(); + video['_isInit'] = false; + renderPreviewFrame(video, frame); + })(); + } + }} +/> + +<Keybinds bind:frame {frameCount} fps={video?.fps} bind:playing bind:playbackStarted /> + +<div class="p-2 w-screen h-screen relative flex flex-col"> + <div class="flex-1 relative"> + <div class="absolute top-0 left-0 w-full h-full flex items-center justify-center"> + <canvas class="pointer-events-none bg-black" bind:this={canvas}> + Your browser doesn't support the canvas API. + </canvas> + {#if audioSource} + <audio src={audioSource} bind:this={audio}></audio> + {/if} + </div> + </div> + <FrameSlider bind:frame {frameCount} bind:playing bind:playbackStarted /> +</div> diff --git a/src/lib/Player/Video.ts b/src/lib/Player/Video.ts new file mode 100644 index 0000000..78b3b8f --- /dev/null +++ b/src/lib/Player/Video.ts @@ -0,0 +1,55 @@ +export type FrameTime = { + milliseconds: number, + seconds: number, + frames: number +} +export abstract class Video { + public constructor(public canvas: HTMLCanvasElement) { }; + public abstract renderFrame(time: FrameTime): Promise<void> | void; + /** (re-)Initializes the Video object. Also called on window resizes. */ + public abstract init(): void | Promise<void>; + private _isInit = false; + /** The frames per second to render at */ + public abstract get fps(): number; + /** Length in frames */ + public abstract get length(): number; + /** A URL (and matching filename) to an ffmpeg-compatible audio file */ + public audioUrl?: readonly [filename: string, fileUrl: string]; + /** Resizes the canvas to a predetermined render resolution - must only be called in init() - do not overwrite */ + public resize(x: number, y: number) { + if (!this._isInit) throw new Error('Must only call resize() in init.') + this.canvas.width = x; + this.canvas.height = y; + const parentW = this.canvas.parentElement!.clientWidth, + parentH = this.canvas.parentElement!.clientHeight + if (x <= parentW && y <= parentH) { + this.canvas.style.width = `${x}px`; + this.canvas.style.height = `${y}px`; + } else if (x <= parentW && y > parentH) { + this.canvas.style.width = `${x / y * parentH}px`; + this.canvas.style.height = `${parentH}px`; + } else if (y <= parentH && x > parentW) { + this.canvas.style.width = `${parentW}px`; + this.canvas.style.height = `${y / x * parentW}px`; + } else { + if ((parentW / x) * y > parentH) { + this.canvas.style.width = `${(parentH / y) * x}px` + this.canvas.style.height = `${parentH}px` + } else { + this.canvas.style.width = `${parentW}px` + this.canvas.style.height = `${(parentW / x) * y}px` + } + } + } + /** The width of the video, in pixels */ + public get w() { + return this.canvas.width; + } + /** The height of the video, in pixels */ + public get h() { + return this.canvas.height; + } + /** Use to cleanup any mess you made - do not remove the canvas, it may be reused. */ + public cleanup() { }; +} +export type VideoConstructor = new (...params: ConstructorParameters<typeof Video>) => Video diff --git a/src/lib/Renderer/Renderer.svelte b/src/lib/Renderer/Renderer.svelte new file mode 100644 index 0000000..72c270b --- /dev/null +++ b/src/lib/Renderer/Renderer.svelte @@ -0,0 +1,146 @@ +<script lang="ts"> + import { onDestroy } from 'svelte'; + import type { VideoConstructor } from '../Player/Video'; + import { FFmpeg, type LogEvent } from '@ffmpeg/ffmpeg'; + import { fetchFile, toBlobURL } from '@ffmpeg/util'; + + let { + Video: VideoImplementation + }: { + Video: VideoConstructor; + } = $props(); + let canvas = $state(null as null | HTMLCanvasElement); + + let frame = $state(0); + let frameCount = $state(0); + let active = false; + + let lastAnimationFrame = 0; + let startedAt = 0; + let message = $state('Waiting'); + let videoUrl = $state(null as null | string); + + const ffmpegBaseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm'; + + const start = async (format = 'mp4') => { + active = true; + startedAt = performance.now(); + + const ffmpeg = new FFmpeg(); + message = 'Loading ffmpeg-core.js'; + ffmpeg.on('log', ({ message: msg }: LogEvent) => { + message = msg; + console.log(message); + }); + await ffmpeg.load({ + coreURL: await toBlobURL(`${ffmpegBaseURL}/ffmpeg-core.js`, 'text/javascript'), + wasmURL: await toBlobURL(`${ffmpegBaseURL}/ffmpeg-core.wasm`, 'application/wasm'), + workerURL: await toBlobURL(`${ffmpegBaseURL}/ffmpeg-core.worker.js`, 'text/javascript') + }); + + message = 'Making directory'; + await ffmpeg.createDir('frames'); + message = 'Preparing Canvas'; + const c = canvas!; + const video = new VideoImplementation(c); + video['_isInit'] = true; + await video.init(); + video['_isInit'] = false; + frameCount = video.length; + message = 'Rendering first few frames...'; + for (frame = 0; frame <= frameCount; frame++) { + if (!active) return; + await video.renderFrame({ + frames: frame, + milliseconds: (frame / video.fps) * 1000, + seconds: frame / video.fps + }); + // TODO: see if we can pipe this into an active ffmpeg instead of writing a file then running a command after + const file = await fetchFile(c.toDataURL()); + await ffmpeg.writeFile('frames/f' + frame.toString().padStart(10, '0') + '.png', file); + if (performance.now() > lastAnimationFrame + 33) { + await new Promise((rs) => requestAnimationFrame(rs)); + lastAnimationFrame = performance.now(); + message = `Rendering to pngs - ${frame}/${frameCount} frames | running for ${( + Math.floor(lastAnimationFrame - startedAt) / 1000 + ).toFixed(3)} seconds`; + } + } + + message = 'Start transcoding'; + await ffmpeg.exec([ + '-framerate', + video.fps.toString(), + '-pattern_type', + 'sequence', + '-start_number', + '0', + '-pattern_type', + 'glob', + '-i', + 'frames/f*.png', + // 'frames/f%04d.png', + 'middle.' + format + ]); + message = 'Disposing pngs'; + for (const i of await ffmpeg.listDir('frames')) + if (!i.isDir) await ffmpeg.deleteFile('frames/' + i.name); + await ffmpeg.deleteDir('frames/'); + if (video.audioUrl) { + message = 'Fetching Audio'; + await ffmpeg.writeFile('audio.' + video.audioUrl[0], await fetchFile(video.audioUrl[1])); + message = 'Merging Audio'; + await ffmpeg.exec([ + '-i', + 'middle.' + format, + '-r', + video.fps.toString(), + '-i', + 'audio.' + video.audioUrl[0], + '-c:v', + 'copy', + '-map', + '0:v:0', + '-map', + '1:a:0', + '-frames:v', + video.length.toString(), + 'output.' + format + ]); + message = 'Removing videoless file'; + await ffmpeg.deleteFile('middle.' + format); + } else await ffmpeg.rename('middle.' + format, 'output.' + format); + message = 'Reading File'; + const data = await ffmpeg.readFile('output.' + format); + console.log('done'); + videoUrl = URL.createObjectURL( + // @ts-ignore bufferlike is good enuf + new Blob([(data as Uint8Array).buffer], { type: 'video/' + format }) + ); + message = 'Disposing ffmpeg state'; + await ffmpeg.deleteFile('output.' + format); + location.href = videoUrl; + }; + $effect(() => { + if (canvas && VideoImplementation) start(); + }); + onDestroy(() => (active = false)); +</script> + +<div class="flex flex-col h-screen w-screen"> + {#if videoUrl} + <!-- svelte-ignore a11y_media_has_caption --> + <video src={videoUrl} controls class="max-w-screen max-h-screen flex-1"></video> + {:else} + <div class="flex-1 relative"> + <div class="absolute top-0 left-0 w-full h-full flex items-center justify-center"> + <canvas class="pointer-events-none bg-black" bind:this={canvas}> + Your browser doesn't support the canvas API. + </canvas> + </div> + </div> + {/if} + <p class="p-4"> + {message} + </p> +</div> diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
\ No newline at end of file diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/vendor/svelte-range-slider/README b/src/lib/vendor/svelte-range-slider/README new file mode 100644 index 0000000..b17797b --- /dev/null +++ b/src/lib/vendor/svelte-range-slider/README @@ -0,0 +1 @@ +https://github.com/roycrippen4/svelte-range-slider/tree/master diff --git a/src/lib/vendor/svelte-range-slider/range-pips.svelte b/src/lib/vendor/svelte-range-slider/range-pips.svelte new file mode 100644 index 0000000..418fc7e --- /dev/null +++ b/src/lib/vendor/svelte-range-slider/range-pips.svelte @@ -0,0 +1,303 @@ +<script lang="ts" module> + export interface PipsProps { + min?: number; + max?: number; + step?: number; + values?: number[]; + vertical?: boolean; + reversed?: boolean; + hoverable?: boolean; + disabled?: boolean; + pipstep?: number; + prefix?: string; + suffix?: string; + focus?: boolean; + range?: undefined | boolean | 'min' | 'max'; + all?: undefined | boolean | 'pip' | 'label'; + first?: boolean | 'pip' | 'label'; + last?: boolean | 'pip' | 'label'; + rest?: boolean | 'pip' | 'label'; + percentOf: (v: number) => number; + fixFloat: (v: number) => number; + orientationStart?: 'top' | 'bottom' | 'left' | 'right'; + orientationEnd?: 'top' | 'bottom' | 'left' | 'right'; + formatter?: (v: number, i: number, p: number) => string; + moveHandle: undefined | ((index: number | undefined, value: number) => number); + normalisedClient: (e: MouseEvent | TouchEvent) => { x: number; y: number }; + } +</script> + +<script lang="ts"> + let { + range = false, + min = 0, + max = 100, + step = 1, + values = [(max + min) / 2], + vertical = false, + reversed = false, + hoverable = true, + disabled = false, + pipstep, + all = true, + first, + last, + rest, + prefix = '', + suffix = '', + focus, + orientationStart, + // eslint-disable-next-line no-unused-vars + formatter = (v, i, p) => v.toString(), + percentOf, + moveHandle, + fixFloat, + normalisedClient + }: PipsProps = $props(); + + let clientStart = $state({ x: 0, y: 0 }); + let pipStep = $derived( + pipstep || + ((max - min) / step >= (vertical ? 50 : 100) ? (max - min) / (vertical ? 10 : 20) : 1) + ); + let pipCount = $derived(parseInt(((max - min) / (step * pipStep)).toString(), 10)); + let pipVal = $derived((val: number) => fixFloat(min + val * step * pipStep)); + let isSelected = $derived((val: number) => values.some((v) => fixFloat(v) === fixFloat(val))); + let inRange = $derived((val: number) => { + if (range === 'min') { + return values[0] > val; + } + if (range === 'max') { + return values[0] < val; + } + if (range) { + return values[0] < val && values[1] > val; + } + }); + + /** + * function to run when the user clicks on a label + * we store the original client position so we can check if the user has moved the mouse/finger + * @param {MouseEvent} e the event from browser + **/ + const labelDown = (e: MouseEvent) => { + clientStart = { x: e.clientX, y: e.clientY }; + }; + + /** + * function to run when the user releases the mouse/finger + * we check if the user has moved the mouse/finger, if not we "click" the label + * and move the handle it to the label position + * @param {number} val the value of the label + * @param {MouseEvent|TouchEvent} e the event from browser + */ + function labelUp(val: number, e: MouseEvent | TouchEvent) { + if (disabled) { + return; + } + + const clientPos = normalisedClient(e); + const distanceMoved = Math.sqrt( + Math.pow(clientStart.x - clientPos.x, 2) + Math.pow(clientStart.y - clientPos.y, 2) + ); + + if (clientStart && distanceMoved <= 5) { + moveHandle?.(undefined, val); + } + } +</script> + +<div + class="rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6" + class:disabled + class:hoverable + class:vertical + class:reversed + class:focus +> + {#if (all && first !== false) || first} + <span + class="pip-680f0f01-664b-43b5-9e1c-789449c63c62 first" + class:selected={isSelected(min)} + class:in-range={inRange(min)} + style="{orientationStart}: 0%;" + onpointerdown={labelDown} + onpointerup={(e) => labelUp(min, e)} + > + {#if all === 'label' || first === 'label'} + <span class="pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b"> + {prefix}{formatter(fixFloat(min), 0, 0)}{suffix} + </span> + {/if} + </span> + {/if} + + {#if (all && rest !== false) || rest} + <!-- eslint-disable-next-line no-unused-vars --> + {#each Array(pipCount + 1) as _, i} + {#if pipVal(i) !== min && pipVal(i) !== max} + <span + class="pip-680f0f01-664b-43b5-9e1c-789449c63c62" + class:selected={isSelected(pipVal(i))} + class:in-range={inRange(pipVal(i))} + style="{orientationStart}: {percentOf(pipVal(i))}%;" + onpointerdown={labelDown} + onpointerup={(e) => labelUp(pipVal(i), e)} + > + {#if all === 'label' || rest === 'label'} + <span class="pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b"> + {prefix}{formatter(pipVal(i), i, percentOf(pipVal(i)))}{suffix} + </span> + {/if} + </span> + {/if} + {/each} + {/if} + + {#if (all && last !== false) || last} + <span + class="pip last" + class:selected={isSelected(max)} + class:in-range={inRange(max)} + style="{orientationStart}: 100%;" + onpointerdown={labelDown} + onpointerup={(e) => labelUp(max, e)} + > + {#if all === 'label' || last === 'label'} + <span class="pipVal"> + {prefix}{formatter(fixFloat(max), pipCount, 100)}{suffix} + </span> + {/if} + </span> + {/if} +</div> + +<style> + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28) { + --pip: var(--range-pip, lightslategray); + --pip-text: var(--range-pip-text, var(--pip)); + --pip-active: var(--range-pip-active, darkslategrey); + --pip-active-text: var(--range-pip-active-text, var(--pip-active)); + --pip-hover: var(--range-pip-hover, darkslategrey); + --pip-hover-text: var(--range-pip-hover-text, var(--pip-hover)); + --pip-in-range: var(--range-pip-in-range, var(--pip-active)); + --pip-in-range-text: var(--range-pip-in-range-text, var(--pip-active-text)); + } + :global(.rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6) { + position: absolute; + height: 1em; + left: 0; + right: 0; + bottom: -1em; + } + :global(.rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.vertical) { + height: auto; + width: 1em; + left: 100%; + right: auto; + top: 0; + bottom: 0; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pip-680f0f01-664b-43b5-9e1c-789449c63c62 + ) { + height: 0.4em; + position: absolute; + top: 0.25em; + width: 1px; + white-space: nowrap; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.vertical + .pip-680f0f01-664b-43b5-9e1c-789449c63c62 + ) { + height: 1px; + width: 0.4em; + left: 0.25em; + top: auto; + bottom: auto; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b + ) { + position: absolute; + top: 0.4em; + transform: translate(-50%, 25%); + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.vertical + .pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b + ) { + position: absolute; + top: 0; + left: 0.4em; + transform: translate(25%, -50%); + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pip-680f0f01-664b-43b5-9e1c-789449c63c62 + ) { + transition: all 0.15s ease; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b + ) { + transition: + all 0.15s ease, + font-weight 0s linear; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pip-680f0f01-664b-43b5-9e1c-789449c63c62 + ) { + color: var(--pip-text, lightslategray); + background-color: var(--pip, lightslategray); + } + :global(.rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pip.selected) { + color: var(--pip-active-text, darkslategrey); + background-color: var(--pip-active, darkslategrey); + } + :global(.rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.hoverable:not(.disabled) .pip:hover) { + color: var(--pip-hover-text, darkslategrey); + background-color: var(--pip-hover, darkslategrey); + } + :global(.rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pip.in-range) { + color: var(--pip-in-range-text, darkslategrey); + background-color: var(--pip-in-range, darkslategrey); + } + :global(.rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 .pip.selected) { + height: 0.75em; + } + :global(.rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.vertical .pip.selected) { + height: 1px; + width: 0.75em; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6 + .pip.selected + .pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b + ) { + font-weight: bold; + top: 0.75em; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.vertical + .pip.selected + .pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b + ) { + top: 0; + left: 0.75em; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.hoverable:not(.disabled) + .pip:not(.selected):hover + ) { + transition: none; + } + :global( + .rangePips-f75c52e3-b799-4c81-8238-035d862cc2e6.hoverable:not(.disabled) + .pip:not(.selected):hover + .pipVal-c41e7185-de59-40a7-90f2-e3d98b1e844b + ) { + transition: none; + font-weight: bold; + } +</style> diff --git a/src/lib/vendor/svelte-range-slider/range-slider.svelte b/src/lib/vendor/svelte-range-slider/range-slider.svelte new file mode 100644 index 0000000..7f522e2 --- /dev/null +++ b/src/lib/vendor/svelte-range-slider/range-slider.svelte @@ -0,0 +1,1026 @@ +<script lang="ts" module> + export type ChangeEvent = { + activeHandle: number; + startValue: number; + previousValue: number; + value: number; + values: number[]; + }; + + export type StartEvent = { activeHandle: number; value: number; values: number[] }; + + export type StopEvent = { + activeHandle: number; + startValue: number; + value: number; + values: number[]; + }; + + export interface RangeSliderProps { + range?: boolean | 'min' | 'max'; + onchange?: (event: ChangeEvent) => void; + onstart?: (event: StartEvent) => void; + onstop?: (event: StopEvent) => void; + pushy?: boolean; + min?: number; + max?: number; + ariaLabels?: string[]; + precision?: number; + springOptions?: { stiffness: number; damping: number }; + id?: string; + prefix?: string; + suffix?: string; + pips?: boolean; + pipstep?: number; + all?: boolean | 'pip' | 'label'; + first?: boolean | 'pip' | 'label'; + last?: boolean | 'pip' | 'label'; + rest?: boolean | 'pip' | 'label'; + step?: number; + value?: number; + values?: number[]; + vertical?: boolean; + float?: boolean; + reversed?: boolean; + hoverable?: boolean; + disabled?: boolean; + formatter?: (value: number, index: number, percent: number) => string; + handleFormatter?: (value: number, index: number, percent: number) => string; + } +</script> + +<script lang="ts"> + import { spring } from 'svelte/motion'; + import RangePips from './range-pips.svelte'; + + let { + range = false, + pushy = false, + min = 0, + max = 100, + ariaLabels = [], + precision = 2, + springOptions = { stiffness: 0.15, damping: 0.4 }, + id = '', + prefix = '', + suffix = '', + pips = false, + pipstep, + all, + first, + last, + rest, + step = 1, + value = $bindable(0), + values = $bindable([(max + min) / 2]), + vertical = false, + float = false, + reversed = false, + hoverable = true, + disabled = false, + onchange, + onstart, + onstop, + formatter = (value: { toString: () => string }) => value.toString(), + handleFormatter = formatter + }: RangeSliderProps = $props(); + + if (value) { + values = [value]; + } + + let slider: Element | undefined = $state(undefined); + let valueLength = $state(0); + let focus = $state(false); + let handleActivated = $state(false); + let handlePressed = $state(false); + let keyboardActive = $state(false); + let activeHandle = $state(values.length - 1); + + let startValue: number | undefined = $state(); + + let previousValue: number | undefined = $state(); + + /** + * make sure the value is coerced to a float value + * @param {number} v the value to fix + * @return {number} a float version of the input + **/ + const fixFloat = (v: number): number => parseFloat((+v).toFixed(precision)); + + $effect(() => { + // check that "values" is an array, or set it as array to prevent any errors in springs, or range trimming + if (!Array.isArray(values)) { + values = [(max + min) / 2]; + console.error( + "'values' prop should be an Array (https://github.com/simeydotme/svelte-range-slider-pips#slider-props)" + ); + } + + // trim the range so it remains as a min/max (only 2 handles) + // and also align the handles to the steps + const trimmedAlignedValues = trimRange(values.map((v) => alignValueToStep(v))); + if ( + !(values.length === trimmedAlignedValues.length) || + !values.every((element, index) => fixFloat(element) === trimmedAlignedValues[index]) + ) { + values = trimmedAlignedValues; + } + + // check if the valueLength (length of values[]) has changed, + // because if so we need to re-seed the spring function with the new values array. + if (valueLength !== values.length) { + // set the initial spring values when the slider initialises, or when values array length has changed + springPositions = spring( + values.map((v) => percentOf(v)), + springOptions + ); + } else { + // update the value of the spring function for animated handles whenever the values has updated + springPositions.set(values.map((v) => percentOf(v))); + } + // set the valueLength for the next check + valueLength = values.length; + + if (values.length > 1 && !Array.isArray(ariaLabels)) { + console.warn( + `'ariaLabels' prop should be an Array (https://github.com/simeydotme/svelte-range-slider-pips#slider-props)` + ); + } + }); + + /** + * take in a value, and then calculate that value's percentage + * of the overall range (min-max); + * @param {number} val the value we're getting percent for + * @return {number} the percentage value + **/ + const percentOf = (/** @type {number} */ val: number): number => { + let percent = ((val - min) / (max - min)) * 100; + + if (isNaN(percent) || percent <= 0) { + return 0; + } + + if (percent >= 100) { + return 100; + } + + return fixFloat(percent); + }; + + /** + * clamp a value from the range so that it always + * falls within the min/max values + * @param {number} val the value to clamp + * @return {number} the value after it's been clamped + **/ + const clampValue = (/** @type {number} */ val: number): number => { + // return the min/max if outside of that range + return val <= min ? min : val >= max ? max : val; + }; + + /** + * align the value with the steps so that it + * always sits on the closest (above/below) step + * @param {number} val the value to align + * @return {number} the value after it's been aligned + **/ + const alignValueToStep = (/** @type {number} */ val: number): number => { + // sanity check for performance + if (val <= min) { + return fixFloat(min); + } + + if (val >= max) { + return fixFloat(max); + } + + val = fixFloat(val); + + // find the middle-point between steps and see if the value is closer to the next step, or previous step + let remainder = (val - min) % step; + let aligned = val - remainder; + + if (Math.abs(remainder) * 2 >= step) { + aligned += remainder > 0 ? step : -step; + } + + aligned = clampValue(aligned); // make sure the value is within acceptable limits + + // make sure the returned value is set to the precision desired + // this is also because javascript often returns weird floats + // when dealing with odd numbers and percentages + return fixFloat(aligned); + }; + + /** + * the orientation of the handles/pips based on the + * input values of vertical and reversed + * @type {"top"|"bottom"|"left"|"right"} orientationStart + **/ + let orientationStart: 'top' | 'bottom' | 'left' | 'right' = $derived( + vertical ? (reversed ? 'top' : 'bottom') : reversed ? 'right' : 'left' + ); + let orientationEnd = $derived( + vertical ? (reversed ? 'bottom' : 'top') : reversed ? 'left' : 'right' + ); + + /** + * helper function to get the index of an element in it's DOM container + * @param {Element|null} el dom object reference we want the index of + * @returns {number} the index of the input element + **/ + function index(el: Element | null): number { + if (!el) { + return -1; + } + + let i = 0; + while ((el = el.previousElementSibling)) { + i++; + } + return i; + } + + /** + * normalise a mouse or touch event to return the + * client (x/y) object for that event + * @param {MouseEvent|TouchEvent} e a mouse/touch event to normalise + * @returns {{ x: number, y: number }} normalised event client object (x,y) + **/ + function normalisedClient(e: MouseEvent | TouchEvent): { x: number; y: number } { + if (e.type.includes('touch')) { + const touchEvent = e as TouchEvent; + const touch = touchEvent.touches[0] || touchEvent.changedTouches[0]; + return { x: touch.clientX, y: touch.clientY }; + } else { + const mouseEvent = e as MouseEvent; + return { x: mouseEvent.clientX, y: mouseEvent.clientY }; + } + } + + /** + * check if an element is a handle on the slider + * @param {Element} el dom object reference we want to check + * @returns {boolean} + **/ + function targetIsHandle(el: Element): boolean { + if (!slider) return false; + const handles = [...slider.querySelectorAll('.handle')]; + const isHandle = handles.includes(el); + const isChild = handles.some((handle) => handle.contains(el)); + return isHandle || isChild; + } + + /** + * trim the values array based on whether the property + * for 'range' is 'min', 'max', or truthy. This is because we + * do not want more than one handle for a min/max range, and we do + * not want more than two handles for a true range. + * @param {number[]} values the input values for the rangeSlider + * @return {number[]} the range array for creating a rangeSlider + **/ + function trimRange(values: number[]): number[] { + if (range === 'min' || range === 'max') { + return values.slice(0, 1); + } + if (range) { + return values.slice(0, 2); + } + + return values; + } + + /** + * helper to return the slider dimensions for finding + * the closest handle to user interaction + * @return {DOMRect} the range slider DOM client rect + **/ + function getSliderDimensions(): DOMRect | undefined { + return slider?.getBoundingClientRect(); + } + + /** + * helper to return closest handle to user interaction + * @param {{ x: number, y: number }} clientPos the client{x,y} positions to check against + * @return {number} the index of the closest handle to clientPos + **/ + function getClosestHandle(clientPos: { x: number; y: number }): number { + // first make sure we have the latest dimensions + // of the slider, as it may have changed size + const dims = getSliderDimensions(); + if (!dims) throw new Error('No Slider Dimensions yet.'); + // calculate the interaction position, percent and value + let handlePos = 0; + let handlePercent = 0; + let handleVal = 0; + if (vertical) { + handlePos = clientPos.y - dims.top; + handlePercent = (handlePos / dims.height) * 100; + handlePercent = reversed ? handlePercent : 100 - handlePercent; + } else { + handlePos = clientPos.x - dims.left; + handlePercent = (handlePos / dims.width) * 100; + handlePercent = reversed ? 100 - handlePercent : handlePercent; + } + handleVal = ((max - min) / 100) * handlePercent + min; + + // if we have a range, and the handles are at the same + // position, we want a simple check if the interaction + // value is greater than return the second handle + if (range === true && values[0] === values[1]) { + if (handleVal > values[1]) { + return 1; + } + + return 0; + + // if there are multiple handles, and not a range, then + // we sort the handles values, and return the first one closest + // to the interaction value + } + + return values.indexOf( + [...values].sort((a, b) => Math.abs(handleVal - a) - Math.abs(handleVal - b))[0] + ); + } + + /** + * take the interaction position on the slider, convert + * it to a value on the range, and then send that value + * through to the moveHandle() method to set the active + * handle's position + * @param {{ x: number, y: number }} clientPos the client{x,y} of the interaction + **/ + function handleInteract(clientPos: { x: number; y: number }) { + // first make sure we have the latest dimensions + // of the slider, as it may have changed size + const dims = getSliderDimensions(); + if (!dims) throw new Error('No Slider Dimensions yet.'); + // calculate the interaction position, percent and value + let handlePos = 0; + let handlePercent = 0; + let handleVal = 0; + if (vertical) { + handlePos = clientPos.y - dims.top; + handlePercent = (handlePos / dims.height) * 100; + handlePercent = reversed ? handlePercent : 100 - handlePercent; + } else { + handlePos = clientPos.x - dims.left; + handlePercent = (handlePos / dims.width) * 100; + handlePercent = reversed ? 100 - handlePercent : handlePercent; + } + handleVal = ((max - min) / 100) * handlePercent + min; + // move handle to the value + moveHandle(activeHandle, handleVal); + } + + let lastSetValue = NaN; + /** + * move a handle to a specific value, respecting the clamp/align rules + * @param {number} index the index of the handle we want to move + * @param {number} handleValue the value to move the handle to + * @return {number} the value that was moved to (after alignment/clamping) + **/ + function moveHandle(index: number | undefined, handleValue: number): number { + // align & clamp the value so we're not doing extra + // calculation on an out-of-range value down below + handleValue = alignValueToStep(handleValue); + // use the active handle if handle index is not provided + if (typeof index === 'undefined') { + index = activeHandle; + } + // if this is a range slider perform special checks + if (range) { + // restrict the handles of a range-slider from + // going past one-another unless "pushy" is true + if (index === 0 && handleValue > values[1]) { + if (pushy) { + values[1] = handleValue; + } else { + handleValue = values[1]; + } + } else if (index === 1 && handleValue < values[0]) { + if (pushy) { + values[0] = handleValue; + } else { + handleValue = values[0]; + } + } + } + + // if the value has changed, update it + if (values[index] !== handleValue) { + values[index] = handleValue; + } + + // fire the change event when the handle moves, + // and store the previous value for the next time + if (previousValue !== handleValue) { + handleOnChange(); + previousValue = handleValue; + } + lastSetValue = handleValue; + value = handleValue; + return handleValue; + } + $effect(() => { + if (value !== lastSetValue) moveHandle(undefined, value); + }); + + /** + * helper to find the beginning range value for use with css style + * @param {number[]} values the input values for the rangeSlider + * @return {number} the beginning of the range + **/ + function rangeStart(values: number[]): number { + if (range === 'min') { + return 0; + } + + return values[0]; + } + + /** + * helper to find the ending range value for use with css style + * @param {array} values the input values for the rangeSlider + * @return {number} the end of the range + **/ + function rangeEnd(values: Array<any>): number { + if (range === 'max') { + return 0; + } + + if (range === 'min') { + return 100 - values[0]; + } + + return 100 - values[1]; + } + + /** + * helper to take a string of html and return only the text + * @param {string} possibleHtml the string that may contain html + * @return {string} the text from the input + */ + function pureText(possibleHtml: string): string { + return `${possibleHtml}`.replace(/<[^>]*>/g, ''); + } + + /** + * when the user has unfocussed (blurred) from the + * slider, deactivate all handles + **/ + function sliderBlurHandle() { + if (!keyboardActive) { + return; + } + + focus = false; + handleActivated = false; + handlePressed = false; + } + + /** + * when the user focusses the handle of a slider + * set it to be active + * @param {Event} e the event from browser + **/ + function sliderFocusHandle(e: Event) { + if (disabled) { + return; + } + + const target = e.target as HTMLElement; + activeHandle = index(target); + focus = true; + } + + /** + * handle the keyboard accessible features by checking the + * input type, and modfier key then moving handle by appropriate amount + * @param {KeyboardEvent} e the event from browser + **/ + function sliderKeydown(e: KeyboardEvent) { + if (disabled) { + return; + } + + const target = e.target as HTMLElement; + const handle = index(target); + let jump = e.ctrlKey || e.metaKey || e.shiftKey ? step * 10 : step; + let prevent = false; + + if (e.key === 'PageDown' || e.key === 'PageUp') { + jump *= 10; + } + + if (e.key === 'ArrowRight' || e.key === 'ArrowUp') { + moveHandle(handle, values[handle] + jump); + prevent = true; + } + + if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') { + moveHandle(handle, values[handle] - jump); + prevent = true; + } + + if (e.key === 'Home') { + moveHandle(handle, min); + prevent = true; + } + + if (e.key === 'End') { + moveHandle(handle, max); + prevent = true; + } + + if (prevent) { + e.preventDefault(); + e.stopPropagation(); + } + } + + /** + * function to run when the user touches the slider element anywhere + * @param {MouseEvent|TouchEvent} e the event from browser + **/ + function sliderInteractStart(e: MouseEvent | TouchEvent) { + if (disabled) { + return; + } + + const element = e.target as HTMLElement; + const clientPos = normalisedClient(e); + // set the closest handle as active + focus = true; + handleActivated = true; + handlePressed = true; + activeHandle = getClosestHandle(clientPos); + + // fire the start event + startValue = previousValue = alignValueToStep(values[activeHandle]); + handleOnStart(); + + // for touch devices we want the handle to instantly + // move to the position touched for more responsive feeling + if (e.type === 'touchstart' && !element.matches('.pipVal')) { + handleInteract(clientPos); + } + } + + /** + * function to run when the user stops touching + * down on the slider element anywhere + * @param {Event} e the event from browser + **/ + function sliderInteractEnd(e: Event) { + // fire the stop event for touch devices + if (e.type === 'touchend') { + handleOnStop(); + } + handlePressed = false; + } + + /** + * unfocus the slider if the user clicked off of + * it, somewhere else on the screen + * @param {MouseEvent|TouchEvent} e the event from browser + **/ + function bodyInteractStart(e: MouseEvent | TouchEvent) { + keyboardActive = false; + const target = e.target as HTMLElement; + if (slider && focus && e.target !== slider && !slider.contains(target)) { + focus = false; + } + } + + /** + * send the clientX through to handle the interaction + * whenever the user moves across screen while active + * @param {MouseEvent|TouchEvent} e the event from browser + **/ + function bodyInteract(e: MouseEvent | TouchEvent) { + if (!disabled) { + if (handleActivated) { + handleInteract(normalisedClient(e)); + } + } + } + + /** + * if user triggers mouseup on the body while + * a handle is active (without moving) then we + * trigger an interact event there + * @param {MouseEvent|TouchEvent} e the event from browser + **/ + function bodyMouseUp(e: MouseEvent | TouchEvent) { + if (!disabled) { + const el = e.target as HTMLElement; + // this only works if a handle is active, which can + // only happen if there was sliderInteractStart triggered + // on the slider, already + if (handleActivated) { + if (slider && (el === slider || slider.contains(el))) { + focus = true; + // don't trigger interact if the target is a handle (no need) or + // if the target is a label (we want to move to that value from rangePips) + if (!targetIsHandle(el) && !el.matches('.pipVal')) { + handleInteract(normalisedClient(e)); + } + } + // fire the stop event for mouse device + // when the body is triggered with an active handle + handleOnStop(); + } + } + handleActivated = false; + handlePressed = false; + } + + /** + * @param {KeyboardEvent} e + */ + function bodyKeyDown(e: KeyboardEvent) { + if (disabled) { + return; + } + + const target = e.target as HTMLElement; + + if (slider && (e.target === slider || slider.contains(target))) { + keyboardActive = true; + } + } + + function handleOnStop() { + if (disabled || !onstop || typeof onstop !== 'function') { + return; + } + + onstop({ + activeHandle, + startValue: startValue ?? 0, + value: values[activeHandle], + values: values.map((v) => alignValueToStep(v)) + }); + } + + function handleOnStart() { + if (disabled || !onstart || typeof onstart !== 'function') { + return; + } + + onstart({ + activeHandle, + value: startValue ?? 0, + values: values.map((v) => alignValueToStep(v)) + }); + } + + function handleOnChange() { + if (disabled || !onchange || typeof onchange !== 'function') { + return; + } + + onchange({ + activeHandle, + startValue: startValue ?? 0, + previousValue: typeof previousValue === 'undefined' ? (startValue ?? 0) : previousValue, + value: values[activeHandle], + values: values.map((v) => alignValueToStep(v)) + }); + } + + /** @type {import('svelte/motion').Spring<number[]>} */ + let springPositions: import('svelte/motion').Spring<number[]> = spring( + values.map((v) => percentOf(v)), + springOptions + ); +</script> + +<div + {id} + bind:this={slider} + role="none" + class="_rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28" + class:range + class:disabled + class:hoverable + class:vertical + class:reversed + class:focus + class:min={range === 'min'} + class:max={range === 'max'} + class:pips + class:pip-labels={all === 'label' || first === 'label' || last === 'label' || rest === 'label'} + onmousedown={sliderInteractStart} + onmouseup={sliderInteractEnd} +> + {#each values as value, index} + <span + role="slider" + class="rangeHandle" + class:active={focus && activeHandle === index} + class:press={handlePressed && activeHandle === index} + data-handle={index} + onblur={sliderBlurHandle} + onfocus={sliderFocusHandle} + onkeydown={sliderKeydown} + style="{orientationStart}: {$springPositions[index]}%; z-index: {activeHandle === index + ? 3 + : 2};" + aria-label={ariaLabels[index]} + aria-valuemin={range === true && index === 1 ? values[0] : min} + aria-valuemax={range === true && index === 0 ? values[1] : max} + aria-valuenow={value} + aria-valuetext="{prefix}{pureText(handleFormatter(value, index, percentOf(value)))}{suffix}" + aria-orientation={vertical ? 'vertical' : 'horizontal'} + aria-disabled={disabled} + tabindex={disabled ? -1 : 0} + > + <span class="rangeNub"></span> + {#if float} + <span class="rangeFloat"> + {prefix}{handleFormatter(value, index, percentOf(value))}{suffix} + </span> + {/if} + </span> + {/each} + + {#if range} + <span + class="rangeBar" + style="{orientationStart}: {rangeStart($springPositions)}%; + {orientationEnd}: {rangeEnd($springPositions)}%;" + ></span> + {/if} + + {#if pips} + <RangePips + {values} + {min} + {max} + {step} + {range} + {vertical} + {reversed} + {orientationStart} + {hoverable} + {disabled} + {all} + {first} + {last} + {rest} + {pipstep} + {prefix} + {suffix} + {formatter} + {focus} + {percentOf} + {moveHandle} + {fixFloat} + {normalisedClient} + /> + {/if} +</div> + +<svelte:window + onmousedown={bodyInteractStart} + onmousemove={bodyInteract} + onmouseup={bodyMouseUp} + onkeydown={bodyKeyDown} +/> + +<style> + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28) { + --slider: var(--range-slider, #d7dada); + --handle-inactive: var(--range-handle-inactive, #99a2a2); + --handle: var(--range-handle, #838de7); + --handle-focus: var(--range-handle-focus, #4a40d4); + --handle-border: var(--range-handle-border, var(--handle)); + --range-inactive: var(--range-range-inactive, var(--handle-inactive)); + --range: var(--range-range, var(--handle-focus)); + --float-inactive: var(--range-float-inactive, var(--handle-inactive)); + --float: var(--range-float, var(--handle-focus)); + --float-text: var(--range-float-text, white); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28) { + position: relative; + border-radius: 100px; + height: 0.5em; + margin: 1em; + transition: opacity 0.2s ease; + user-select: none; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 *) { + user-select: none; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.pips) { + margin-bottom: 1.8em; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.pip-labels) { + margin-bottom: 2.8em; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.vertical) { + display: inline-block; + border-radius: 100px; + width: 0.5em; + min-height: 200px; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.vertical.pips) { + margin-right: 1.8em; + margin-bottom: 1em; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.vertical.pip-labels) { + margin-right: 2.8em; + margin-bottom: 1em; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeHandle) { + position: absolute; + display: block; + height: 1.4em; + width: 1.4em; + top: 0.25em; + bottom: auto; + transform: translateY(-50%) translateX(-50%); + z-index: 2; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.reversed .rangeHandle) { + transform: translateY(-50%) translateX(50%); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.vertical .rangeHandle) { + left: 0.25em; + top: auto; + transform: translateY(50%) translateX(-50%); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.vertical.reversed .rangeHandle) { + transform: translateY(-50%) translateX(-50%); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeNub), + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeHandle:before) { + position: absolute; + left: 0; + top: 0; + display: block; + border-radius: 10em; + height: 100%; + width: 100%; + transition: box-shadow 0.2s ease; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeHandle:before) { + content: ''; + left: 1px; + top: 1px; + bottom: 1px; + right: 1px; + height: auto; + width: auto; + box-shadow: 0 0 0 0px var(--handle-border); + opacity: 0; + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.hoverable:not(.disabled) + .rangeHandle:hover:before + ) { + box-shadow: 0 0 0 8px var(--handle-border); + opacity: 0.2; + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.hoverable:not(.disabled) + .rangeHandle.press:before + ), + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.hoverable:not(.disabled) + .rangeHandle.press:hover:before + ) { + box-shadow: 0 0 0 12px var(--handle-border); + opacity: 0.4; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range:not(.min):not(.max) .rangeNub) { + border-radius: 10em 10em 10em 1.6em; + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range .rangeHandle:nth-of-type(1) .rangeNub + ) { + transform: rotate(-135deg); + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range .rangeHandle:nth-of-type(2) .rangeNub + ) { + transform: rotate(45deg); + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range.reversed + .rangeHandle:nth-of-type(1) + .rangeNub + ) { + transform: rotate(45deg); + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range.reversed + .rangeHandle:nth-of-type(2) + .rangeNub + ) { + transform: rotate(-135deg); + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range.vertical + .rangeHandle:nth-of-type(1) + .rangeNub + ) { + transform: rotate(135deg); + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range.vertical + .rangeHandle:nth-of-type(2) + .rangeNub + ) { + transform: rotate(-45deg); + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range.vertical.reversed + .rangeHandle:nth-of-type(1) + .rangeNub + ) { + transform: rotate(-45deg); + } + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.range.vertical.reversed + .rangeHandle:nth-of-type(2) + .rangeNub + ) { + transform: rotate(135deg); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeFloat) { + display: block; + position: absolute; + left: 50%; + top: -0.5em; + transform: translate(-50%, -100%); + font-size: 1em; + text-align: center; + opacity: 0; + pointer-events: none; + white-space: nowrap; + transition: all 0.2s ease; + font-size: 0.9em; + padding: 0.2em 0.4em; + border-radius: 0.2em; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeHandle.active .rangeFloat), + :global( + ._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.hoverable .rangeHandle:hover .rangeFloat + ) { + opacity: 1; + top: -0.2em; + transform: translate(-50%, -100%); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeBar) { + position: absolute; + display: block; + transition: background 0.2s ease; + border-radius: 1em; + height: 0.5em; + top: 0; + user-select: none; + z-index: 1; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.vertical .rangeBar) { + width: 0.5em; + height: auto; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28) { + background-color: var(--slider, #d7dada); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeBar) { + background-color: var(--range-inactive, #99a2a2); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.focus .rangeBar) { + background-color: var(--range, #838de7); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeNub) { + background-color: var(--handle-inactive, #99a2a2); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.focus .rangeNub) { + background-color: var(--handle, #838de7); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeHandle.active .rangeNub) { + background-color: var(--handle-focus, #4a40d4); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28 .rangeFloat) { + color: white; + color: var(--float-text); + background-color: var(--float-inactive, #99a2a2); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.focus .rangeFloat) { + background-color: var(--float, #4a40d4); + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.disabled) { + opacity: 0.5; + } + :global(._rangeslider-0f6d4a99-47b0-4108-8415-b2aefa867e28.disabled .rangeNub) { + background-color: var(--slider, #d7dada); + } +</style> diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..8c56a3c --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,12 @@ +<script lang="ts"> + import '../app.css'; + import favicon from '$lib/assets/favicon.svg'; + + let { children } = $props(); +</script> + +<svelte:head> + <link rel="icon" href={favicon} /> +</svelte:head> + +{@render children?.()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..8d09da5 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,6 @@ +<script lang="ts"> + import Editor from '$/lib/Player/Player.svelte'; + import Video from '$/user'; +</script> + +<Editor {Video} /> diff --git a/src/routes/ffmpeg-test/+page.svelte b/src/routes/ffmpeg-test/+page.svelte new file mode 100644 index 0000000..226fc3c --- /dev/null +++ b/src/routes/ffmpeg-test/+page.svelte @@ -0,0 +1,45 @@ +<script lang="ts"> + import { FFmpeg } from '@ffmpeg/ffmpeg'; + // @ts-ignore + import type { LogEvent } from '@ffmpeg/ffmpeg/dist/esm/types'; + import { fetchFile, toBlobURL } from '@ffmpeg/util'; + + let videoEl: HTMLVideoElement; + + const baseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm'; + const videoURL = 'https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi'; + + let message = $state('Click Start to Transcode'); + + const transcode = async () => { + const ffmpeg = new FFmpeg(); + message = 'Loading ffmpeg-core.js'; + ffmpeg.on('log', ({ message: msg }: LogEvent) => { + message = msg; + console.log(message); + }); + await ffmpeg.load({ + coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'), + wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'), + workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript') + }); + message = 'Start transcoding'; + await ffmpeg.writeFile('test.avi', await fetchFile(videoURL)); + await ffmpeg.exec(['-i', 'test.avi', 'test.mp4']); + message = 'Complete transcoding'; + const data = await ffmpeg.readFile('test.mp4'); + console.log('done'); + videoEl.src = URL.createObjectURL( + // @ts-ignore bufferlike is good enuf + new Blob([(data as Uint8Array).buffer], { type: 'video/mp4' }) + ); + }; +</script> + +<div> + <!-- svelte-ignore a11y_media_has_caption --> + <video bind:this={videoEl} controls></video> + <br /> + <button onclick={transcode}>Start</button> + <p>{message}</p> +</div> diff --git a/src/routes/render/+page.svelte b/src/routes/render/+page.svelte new file mode 100644 index 0000000..e9ecd91 --- /dev/null +++ b/src/routes/render/+page.svelte @@ -0,0 +1,6 @@ +<script lang="ts"> + import Renderer from '$/lib/Renderer/Renderer.svelte'; + import Video from '$/user'; +</script> + +<Renderer {Video} /> diff --git a/src/user/Sneky Snitch.mp4 b/src/user/Sneky Snitch.mp4 Binary files differnew file mode 100644 index 0000000..b9d53b5 --- /dev/null +++ b/src/user/Sneky Snitch.mp4 diff --git a/src/user/index.ts b/src/user/index.ts new file mode 100644 index 0000000..2caf88c --- /dev/null +++ b/src/user/index.ts @@ -0,0 +1,21 @@ +import { Video as BaseVideo, type FrameTime } from '$/lib/Player/Video'; +import SneakySnitchUrl from './Sneky Snitch.mp4?url' + +export default class Video extends BaseVideo { + public ctx!: CanvasRenderingContext2D + public init(): void | Promise<void> { + // this.resize(this.canvas.clientWidth,this.canvas.clientHeight) + this.resize(1920, 1080) + this.ctx = this.canvas.getContext('2d')! + } + public renderFrame(time: FrameTime): Promise<void> | void { + this.ctx.fillStyle = '#000' + this.ctx.fillRect(0, 0, this.w, this.h) + this.ctx.font = "50px Nunito"; + this.ctx.fillStyle = '#fff' + this.ctx.fillText(`${time.seconds.toFixed(3)}`, 0, 50) + } + public fps = 30; + public length = 3 * this.fps; + public audioUrl = ['sneakysnitch.mp4', SneakySnitchUrl] as const +} diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..0568a73 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,30 @@ +import adapter from '@sveltejs/adapter-static'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; +import importMap from './import_map.json' with { type: 'json' }; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + paths: { + base: '', + }, + adapter: adapter({ + fallback: '404.html', + }), + alias: Object.fromEntries( + Object.entries(importMap.imports) + .filter(([k]) => k !== '$lib' && k !== '$lib/') + .map(([k, v]) => + v.startsWith('npm:') + ? 'npm:' + k === v + ? [] + : [k, 'node_modules/' + v.substring(4)] + : [k, v], + ) + .filter((v) => v.length !== 0), + ), + }, +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c369c4c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias + // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +}
\ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d2f5e0a --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import { defineConfig } from "vite"; +import tailwindcss from '@tailwindcss/vite' + +const viteServerConfig: import('vite').Plugin = { + name: 'cors', + configureServer(server) { + server.middlewares.use((req, res, next) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET"); + res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + next(); + }); + } +}; + +export default defineConfig({ + plugins: [sveltekit(), tailwindcss(), viteServerConfig], + optimizeDeps: { + exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util'] + }, +}); |