chore: add website source

This commit is contained in:
edde746
2026-05-18 13:15:17 +02:00
parent d3d7957b62
commit d004342c98
52 changed files with 4201 additions and 0 deletions
+23
View File
@@ -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-*
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+24
View File
@@ -0,0 +1,24 @@
# Plezy Website
Source for [plezy.app](https://plezy.app), built with SvelteKit and exported as a static site.
## Development
```bash
bun install
bun run dev
```
## Checks
```bash
bun run check
```
## Build
```bash
bun run build
```
The production output is written to `build/` and is intentionally ignored by git.
+532
View File
@@ -0,0 +1,532 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "plezy-site",
"dependencies": {
"@sveltejs/adapter-static": "^3.0.10",
},
"devDependencies": {
"@iconify/json": "^2.2.474",
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/enhanced-img": "^0.10.4",
"@sveltejs/kit": "^2.50.1",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"google-play-scraper": "^10.1.2",
"svelte": "^5.48.2",
"svelte-check": "^4.3.5",
"typescript": "^5.9.3",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1",
},
},
},
"packages": {
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
"@iconify/json": ["@iconify/json@2.2.474", "", { "dependencies": { "@iconify/types": "*", "pathe": "^2.0.3" } }, "sha512-6dyYYjYWMcsV3nWMX4rEtDypaSL1+j6M7ra7F+CMLlpCSy8qdgYUDcwr7FrIYSa+GySmRfhdtJcUPQYWvB4DRw=="],
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
"@iconify/utils": ["@iconify/utils@3.1.3", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw=="],
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.8", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA=="],
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.0", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw=="],
"@sveltejs/adapter-static": ["@sveltejs/adapter-static@3.0.10", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew=="],
"@sveltejs/enhanced-img": ["@sveltejs/enhanced-img@0.10.4", "", { "dependencies": { "magic-string": "^0.30.5", "sharp": "^0.34.1", "svelte-parse-markup": "^0.1.5", "vite-imagetools": "^9.0.3", "zimmerframe": "^1.1.2" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0 || ^7.0.0", "svelte": "^5.0.0", "vite": "^6.3.0 || >=7.0.0" } }, "sha512-Am5nmAKUo7Nboqq7Dhtfn7dcXA087d7gIz6Vecn1opB41aJ680+0q9U9KvEcMgduOyeiwckTIOQOx4Mmq9GcvA=="],
"@sveltejs/kit": ["@sveltejs/kit@2.50.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.2", "", { "dependencies": { "obug": "^2.1.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig=="],
"@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="],
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="],
"@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
"@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="],
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="],
"cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="],
"cheerio": ["cheerio@1.2.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg=="],
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
"d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
"debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
"es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
"es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
"es6-weak-map": ["es6-weak-map@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.46", "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.1" } }, "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA=="],
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
"esrap": ["esrap@2.2.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
"exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="],
"ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"google-play-scraper": ["google-play-scraper@10.1.2", "", { "dependencies": { "cheerio": "^1.0.0-rc.12", "debug": "^3.1.0", "got": "^11.8.6", "memoizee": "^0.4.14", "ramda": "^0.29.0", "tough-cookie": "^4.1.3" } }, "sha512-jDgJlIXc4T785jUyD7qTTZ1i5GrJjZuJ3lxkLiUqQubxDFt669xygoRxiBmeWPRX10SWJt1rK3Mb6psO5y9xCw=="],
"got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="],
"htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
"http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="],
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"imagetools-core": ["imagetools-core@9.1.0", "", {}, "sha512-xQjs+2vrxLnAjCq+omuNkd5UQTld9/bP8+YT0LyYTlKfuSQtgUBvqhUwGugzSAh6sCdN+LnROMuLswn5hZ9Fhg=="],
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
"is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="],
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
"local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
"lru-queue": ["lru-queue@0.1.0", "", { "dependencies": { "es5-ext": "~0.10.2" } }, "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"memoizee": ["memoizee@0.4.17", "", { "dependencies": { "d": "^1.0.2", "es5-ext": "^0.10.64", "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", "is-promise": "^2.2.2", "lru-queue": "^0.1.0", "next-tick": "^1.1.0", "timers-ext": "^0.1.7" } }, "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
"normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
"parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="],
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
"querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="],
"quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="],
"ramda": ["ramda@0.29.1", "", {}, "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="],
"resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],
"responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="],
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
"set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"svelte": ["svelte@5.49.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.2", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-PYLwnngYzyhKzqDlGVlCH4z+NVI8mC0/bTv15vw25CcdOhxENsOHIbQ36oj5DIf3oBazM+STbCAvaskpxtBmWA=="],
"svelte-check": ["svelte-check@4.3.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q=="],
"svelte-parse-markup": ["svelte-parse-markup@0.1.5", "", { "peerDependencies": { "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1" } }, "sha512-T6mqZrySltPCDwfKXWQ6zehipVLk4GWfH1zCMGgRtLlOIFPuw58ZxVYxVvotMJgJaurKi1i14viB2GIRKXeJTQ=="],
"timers-ext": ["timers-ext@0.1.8", "", { "dependencies": { "es5-ext": "^0.10.64", "next-tick": "^1.1.0" } }, "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww=="],
"tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
"tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="],
"undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
"unplugin-icons": ["unplugin-icons@23.0.1", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/utils": "^3.1.0", "local-pkg": "^1.1.2", "obug": "^2.1.1", "unplugin": "^2.3.11" }, "peerDependencies": { "@svgr/core": ">=7.0.0", "@svgx/core": "^1.0.1", "@vue/compiler-sfc": "^3.0.2", "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["@svgr/core", "@svgx/core", "@vue/compiler-sfc", "svelte"] }, "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ=="],
"url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vite-imagetools": ["vite-imagetools@9.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.5", "imagetools-core": "^9.1.0", "sharp": "^0.34.1" } }, "sha512-FwjApRNZyN+RucPW9Z9kf0dyzyi3r3zlDfrTnzHXNaYpmT3pZ5w//d6QkApy1iypbDm+3fq+Gwfv+PYA4j4uYw=="],
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
"whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
"clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
"mlly/acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "plezy-site",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@iconify/json": "^2.2.474",
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/enhanced-img": "^0.10.4",
"@sveltejs/kit": "^2.50.1",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"google-play-scraper": "^10.1.2",
"svelte": "^5.48.2",
"svelte-check": "^4.3.5",
"typescript": "^5.9.3",
"unplugin-icons": "^23.0.1",
"vite": "^7.3.1"
},
"dependencies": {
"@sveltejs/adapter-static": "^3.0.10"
}
}
+15
View File
@@ -0,0 +1,15 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
import "unplugin-icons/types/svelte";
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#0e0f12" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 32 358.32 399.86'>
<defs>
<linearGradient id='logo-gradient' x1='22.67' y1='425.72' x2='201.83' y2='115.4' gradientUnits='userSpaceOnUse'>
<stop offset='0' stop-color='#ab543a'/>
<stop offset='1' stop-color='#ff7e57'/>
</linearGradient>
</defs>
<path fill='url(#logo-gradient)' d='M335.65,192.66L68.01,38.14C37.78,20.69,0,42.5,0,77.41v309.04c0,34.91,37.78,56.72,68.01,39.27l267.64-154.52c30.23-17.45,30.23-61.08,0-78.54ZM255.53,276.8c-19.1,17.66-38.75,26.49-58.4,26.49s-39.29-8.83-58.39-26.49c-14.39-13.3-28.55-20.05-42.11-20.05s-27.72,6.75-42.1,20.05c-4.87,4.5-12.46,4.2-16.96-.67-4.5-4.86-4.2-12.45.67-16.95,38.2-35.33,78.59-35.33,116.79,0,14.38,13.3,28.55,20.04,42.1,20.04s27.72-6.74,42.11-20.04c4.86-4.5,12.46-4.21,16.95.66,4.5,4.87,4.21,12.46-.66,16.96ZM255.53,204.04c-19.1,17.67-38.75,26.5-58.4,26.5s-39.29-8.83-58.39-26.5c-14.39-13.3-28.55-20.04-42.11-20.04s-27.72,6.74-42.1,20.04c-4.87,4.5-12.46,4.21-16.96-.66s-4.2-12.46.67-16.96c38.2-35.32,78.59-35.32,116.79,0,14.38,13.3,28.55,20.05,42.1,20.05s27.72-6.75,42.11-20.05c4.86-4.5,12.46-4.2,16.95.67,4.5,4.86,4.21,12.45-.66,16.95Z'/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 32 358.32 399.86'>
<defs>
<linearGradient id='logo-gradient' x1='22.67' y1='425.72' x2='201.83' y2='115.4' gradientUnits='userSpaceOnUse'>
<stop offset='0' stop-color='#ab543a'/>
<stop offset='1' stop-color='#ff7e57'/>
</linearGradient>
</defs>
<path fill='url(#logo-gradient)' d='M335.65,192.66L68.01,38.14C37.78,20.69,0,42.5,0,77.41v309.04c0,34.91,37.78,56.72,68.01,39.27l267.64-154.52c30.23-17.45,30.23-61.08,0-78.54ZM255.53,276.8c-19.1,17.66-38.75,26.49-58.4,26.49s-39.29-8.83-58.39-26.49c-14.39-13.3-28.55-20.05-42.11-20.05s-27.72,6.75-42.1,20.05c-4.87,4.5-12.46,4.2-16.96-.67-4.5-4.86-4.2-12.45.67-16.95,38.2-35.33,78.59-35.33,116.79,0,14.38,13.3,28.55,20.04,42.1,20.04s27.72-6.74,42.11-20.04c4.86-4.5,12.46-4.21,16.95.66,4.5,4.87,4.21,12.46-.66,16.96ZM255.53,204.04c-19.1,17.67-38.75,26.5-58.4,26.5s-39.29-8.83-58.39-26.5c-14.39-13.3-28.55-20.04-42.11-20.04s-27.72,6.74-42.1,20.04c-4.87,4.5-12.46,4.21-16.96-.66s-4.2-12.46.67-16.96c38.2-35.32,78.59-35.32,116.79,0,14.38,13.3,28.55,20.05,42.1,20.05s27.72-6.75,42.11-20.05c4.86-4.5,12.46-4.2,16.95.67,4.5,4.86,4.21,12.45-.66,16.95Z'/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

@@ -0,0 +1,270 @@
<script lang="ts">
import AppleIcon from "~icons/simple-icons/apple";
import GooglePlayIcon from "~icons/simple-icons/googleplay";
import LinuxIcon from "~icons/simple-icons/linux";
import AmazonIcon from "~icons/cib/amazon";
import ChevronDownIcon from "~icons/heroicons/chevron-down-solid";
import WindowsIcon from "./WindowsIcon.svelte";
const linuxArchitectures = [
{
label: "x64 (Intel/AMD)",
formats: [
{ label: ".deb (Debian/Ubuntu)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-x64.deb" },
{ label: ".rpm (Fedora/RHEL)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-x64.rpm" },
{ label: ".pkg.tar.zst (Arch)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-x64.pkg.tar.zst" },
{ label: ".tar.gz (Portable)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-x64.tar.gz" },
],
},
{
label: "ARM64",
formats: [
{ label: ".deb (Debian/Ubuntu)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-arm64.deb" },
{ label: ".rpm (Fedora/RHEL)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-arm64.rpm" },
{ label: ".pkg.tar.zst (Arch)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-arm64.pkg.tar.zst" },
{ label: ".tar.gz (Portable)", url: "https://github.com/edde746/plezy/releases/latest/download/plezy-linux-arm64.tar.gz" },
],
},
];
let linuxOpen = $state(false);
let hovered = $state(false);
let showDropdown = $derived(linuxOpen || hovered);
</script>
<svelte:window onclick={() => { linuxOpen = false; }} />
<div class="download-buttons">
<!-- Primary row -->
<div class="store-buttons">
<a
href="https://apps.apple.com/us/app/id6754315964"
target="_blank"
rel="noopener noreferrer"
class="store-button"
>
<AppleIcon />
App Store
</a>
<a
href="https://play.google.com/store/apps/details?id=com.edde746.plezy"
target="_blank"
rel="noopener noreferrer"
class="store-button"
>
<GooglePlayIcon />
Google Play
</a>
<a
href="https://www.amazon.com/gp/product/B0GK65CVS1"
target="_blank"
rel="noopener noreferrer"
class="store-button"
>
<AmazonIcon />
Fire TV
</a>
</div>
<!-- Desktop row -->
<div class="desktop-buttons">
<a
href="https://github.com/edde746/plezy/releases/latest/download/plezy-windows-installer.exe"
class="desktop-button"
>
<WindowsIcon />
Windows
</a>
<a
href="https://github.com/edde746/plezy/releases/latest/download/plezy-macos.dmg"
class="desktop-button"
>
<AppleIcon />
macOS
</a>
<!-- Linux dropdown -->
<div
class="linux-control"
role="group"
onpointerenter={(e) => { if (e.pointerType === 'mouse') hovered = true; }}
onpointerleave={(e) => { if (e.pointerType === 'mouse') hovered = false; }}
>
<button
type="button"
onclick={(e) => { e.stopPropagation(); linuxOpen = !linuxOpen; }}
aria-expanded={showDropdown}
aria-haspopup="true"
class="desktop-button linux-button"
class:active={showDropdown}
>
<LinuxIcon />
Linux
<span class="chevron" class:open={showDropdown}>
<ChevronDownIcon />
</span>
</button>
<div
role="menu"
class="linux-menu"
class:open={showDropdown}
>
{#each linuxArchitectures as arch, i}
{#if i > 0}
<div class="linux-separator"></div>
{/if}
<div class="linux-arch-label">{arch.label}</div>
{#each arch.formats as format}
<a href={format.url} role="menuitem" onclick={() => { linuxOpen = false; }} class="linux-menu-item">
{format.label}
</a>
{/each}
{/each}
</div>
</div>
</div>
</div>
<style>
.download-buttons {
display: flex;
flex-direction: column;
gap: 1rem;
}
.store-buttons,
.desktop-buttons {
display: flex;
flex-wrap: wrap;
}
.store-buttons {
gap: 0.75rem;
}
.desktop-buttons {
gap: 0.625rem;
}
.store-button,
.desktop-button {
display: inline-flex;
align-items: center;
border-radius: 9999px;
font-size: 0.875rem;
line-height: 1.25rem;
transition: background-color 150ms ease, color 150ms ease;
}
.store-button {
gap: 0.625rem;
padding: 0.75rem 1.25rem;
color: #111827;
background: #fff;
font-weight: 600;
}
.store-button:hover {
background: #f3f4f6;
}
.store-button :global(svg) {
width: 1.25rem;
height: 1.25rem;
}
.desktop-button {
gap: 0.5rem;
padding: 0.5rem 1rem;
border: 1px solid var(--color-border);
background: color-mix(in srgb, var(--color-surface) 80%, transparent);
}
.desktop-button:hover,
.linux-button.active {
background: var(--color-surface-hover);
}
.desktop-button :global(svg) {
width: 0.875rem;
height: 0.875rem;
opacity: 0.7;
}
.linux-control {
position: relative;
}
.linux-button {
cursor: default;
}
.chevron {
width: 0.75rem;
height: 0.75rem;
transition: transform 300ms ease;
}
.chevron.open {
transform: rotate(180deg);
}
.chevron :global(svg) {
width: 0.75rem;
height: 0.75rem;
opacity: 1;
}
.linux-menu {
position: absolute;
bottom: 100%;
left: 0;
z-index: 10;
width: 14rem;
margin-bottom: 0.5rem;
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: 1rem;
background: var(--color-surface);
box-shadow:
0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);
opacity: 0;
visibility: hidden;
transition: opacity 150ms ease, visibility 150ms ease;
}
.linux-menu.open {
opacity: 1;
visibility: visible;
}
.linux-separator {
border-top: 1px solid var(--color-border);
}
.linux-arch-label {
padding: 0.625rem 1rem 0.25rem;
color: var(--color-text-muted);
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.025em;
text-transform: uppercase;
}
.linux-menu-item {
display: block;
padding: 0.5rem 1rem;
font-size: 0.875rem;
line-height: 1.25rem;
transition: background-color 150ms ease;
}
.linux-menu-item:hover {
background: var(--color-surface-hover);
}
</style>
+189
View File
@@ -0,0 +1,189 @@
<script lang="ts">
import { page } from '$app/state';
import { faqs } from '$lib/content/faqs';
import MinusIcon from '~icons/heroicons/minus';
import PlusIcon from '~icons/heroicons/plus';
import ScrollReveal from "./ScrollReveal.svelte";
const hash = $derived(page.url.hash.slice(1));
const hashIndex = $derived(faqs.findIndex((f) => f.id === hash));
let openIndex = $state<number | null>(null);
$effect(() => {
if (hashIndex !== -1) {
openIndex = hashIndex;
requestAnimationFrame(() => {
document.getElementById(hash)?.scrollIntoView({ behavior: "smooth", block: "center" });
});
}
});
function toggle(index: number) {
openIndex = openIndex === index ? null : index;
}
</script>
<section id="faq" class="faq-section">
<ScrollReveal>
<p class="section-label">FAQ</p>
<h2 class="section-heading">Common questions</h2>
<p class="section-description">Everything you need to know about Plezy.</p>
</ScrollReveal>
<div class="faq-list">
{#each faqs as faq, i}
<ScrollReveal delay={i * 50}>
<div id={faq.id} class="glass-card faq-card">
<button
type="button"
class="faq-toggle"
onclick={() => toggle(i)}
aria-expanded={openIndex === i}
aria-controls={`${faq.id}-answer`}
>
<span class="faq-question">{faq.question}</span>
<span class="faq-icon">
{#if openIndex === i}
<MinusIcon />
{:else}
<PlusIcon />
{/if}
</span>
</button>
<div
id={`${faq.id}-answer`}
class="faq-answer"
class:open={openIndex === i}
aria-hidden={openIndex !== i}
inert={openIndex !== i}
>
<div>
<div class="faq-answer-content">
{@html faq.answer}
</div>
</div>
</div>
</div>
</ScrollReveal>
{/each}
</div>
</section>
<style>
.faq-section {
max-width: 64rem;
margin-inline: auto;
padding: 4rem 1.5rem;
}
.section-label {
margin-bottom: 0.75rem;
color: var(--color-accent);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.025em;
line-height: 1.25rem;
text-transform: uppercase;
}
.section-heading {
margin-bottom: 1rem;
font-size: 2.25rem;
font-weight: 700;
line-height: 2.5rem;
}
.section-description {
max-width: 32rem;
margin-bottom: 2.5rem;
color: var(--color-text-muted);
font-size: 1.125rem;
line-height: 1.75rem;
}
.faq-list {
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.faq-card {
border-radius: 1rem;
}
.faq-toggle {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 1.5rem;
text-align: left;
}
.faq-question {
font-weight: 500;
}
.faq-icon {
flex-shrink: 0;
width: 1.25rem;
height: 1.25rem;
color: var(--color-accent);
}
.faq-icon :global(svg) {
width: 1.25rem;
height: 1.25rem;
}
.faq-answer {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 200ms ease;
}
.faq-answer.open {
grid-template-rows: 1fr;
}
.faq-answer > div {
overflow: hidden;
min-height: 0;
}
.faq-answer-content {
padding: 0 1.5rem 1rem;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.625;
}
.faq-answer-content :global(a) {
color: var(--color-accent);
}
.faq-answer-content :global(a:hover) {
text-decoration: underline;
}
@media (min-width: 640px) {
.faq-section {
padding-block: 6rem;
}
.section-description {
margin-bottom: 4rem;
}
}
@media (min-width: 768px) {
.faq-section {
padding-block: 8rem;
}
.section-heading {
font-size: 3rem;
line-height: 1;
}
}
</style>
+730
View File
@@ -0,0 +1,730 @@
<script lang="ts">
import ScrollReveal from "./ScrollReveal.svelte";
import AppleIcon from "~icons/simple-icons/apple";
import GooglePlayIcon from "~icons/simple-icons/googleplay";
import LinuxIcon from "~icons/simple-icons/linux";
import AniListIcon from "~icons/simple-icons/anilist";
import MyAnimeListIcon from "~icons/simple-icons/myanimelist";
import SimklIcon from "~icons/simple-icons/simkl";
import TraktIcon from "~icons/simple-icons/trakt";
import WindowsIcon from "./WindowsIcon.svelte";
</script>
<section id="features" class="features-section">
<ScrollReveal>
<p class="section-label">Features</p>
<h2 class="section-heading">Everything you need to stream</h2>
<p class="section-description">
Built for people who care about quality as much as convenience.
</p>
</ScrollReveal>
<div class="bento-grid">
<!-- HERO: Wide Codec Support — 2col × 2row -->
<ScrollReveal class="bento-codec">
<div class="glass-card feature-card codec-card">
<div class="card-content">
<h3 class="card-title large">Wide Codec Support</h3>
<p class="card-copy codec-copy">
Play without transcoding. Your server does less work — you get better quality.
</p>
</div>
<!-- Codec wall with accent highlights on key codecs -->
<div class="codec-wall" aria-hidden="true">
<span class="codec-accent">HEVC</span>
<span class="codec-accent-80">AV1</span>
<span class="codec-text-70">H.264</span>
<span class="codec-text-70">VP9</span>
<span class="codec-accent-60">AAC</span>
<span class="codec-text-50">FLAC</span>
<span class="codec-text-50">OPUS</span>
<span class="codec-text-50">EAC3</span>
<span class="codec-text-40">DTS</span>
<span class="codec-text-40">MPEG-4</span>
<span class="codec-text-30">VP8</span>
<span class="codec-text-30">ALAC</span>
<span class="codec-text-30">TrueHD</span>
<span class="codec-text-20">MPEG-2</span>
<span class="codec-text-20">VC-1</span>
<span class="codec-text-20">AC3</span>
<span class="codec-text-15">PCM</span>
<span class="codec-text-10">Vorbis</span>
<span class="codec-text-10">MP3</span>
<span class="codec-text-10">WMA</span>
</div>
<div class="codec-fade"></div>
</div>
</ScrollReveal>
<!-- HDR -->
<ScrollReveal delay={50} class="bento-hdr">
<div class="glass-card feature-card hdr-card">
<!-- Subtle warm glow -->
<div class="hdr-glow"></div>
<p class="card-eyebrow">Display</p>
<div class="card-bottom">
<p class="hdr-title">
HDR10+
</p>
<p class="hdr-subtitle">Dolby Vision</p>
</div>
</div>
</ScrollReveal>
<!-- Advanced Subtitles -->
<ScrollReveal delay={100} class="bento-sub">
<div class="glass-card feature-card sub-card">
<!-- Fake subtitle lines -->
<div class="subtitle-lines" aria-hidden="true">
<div class="subtitle-line wide"></div>
<div class="subtitle-line short"></div>
</div>
<div>
<p class="card-title">Subtitles</p>
<p class="card-caption">SSA/ASS &middot; SRT &middot; PGS</p>
</div>
</div>
</ScrollReveal>
<!-- Cross-Platform — 1col × 2row tall -->
<ScrollReveal delay={80} class="bento-cross">
<div class="glass-card feature-card cross-card">
<p class="card-title with-gap">Cross-Platform</p>
<div class="platform-list">
<div class="platform-row">
<div class="platform-icon-box">
<AppleIcon />
</div>
<span>iOS &middot; macOS</span>
</div>
<div class="platform-row">
<div class="platform-icon-box">
<GooglePlayIcon />
</div>
<span>Android</span>
</div>
<div class="platform-row">
<div class="platform-icon-box">
<WindowsIcon />
</div>
<span>Windows</span>
</div>
<div class="platform-row">
<div class="platform-icon-box">
<LinuxIcon />
</div>
<span>Linux</span>
</div>
</div>
</div>
</ScrollReveal>
<!-- Offline Downloads -->
<ScrollReveal delay={150} class="bento-offline">
<div class="glass-card feature-card offline-card">
<p class="card-title">Offline</p>
<!-- Download progress visual -->
<div class="progress-block" aria-hidden="true">
<div class="progress-track">
<div class="progress-fill"></div>
</div>
<p class="progress-caption">Download &amp; watch anywhere</p>
</div>
</div>
</ScrollReveal>
<!-- Watch Together -->
<ScrollReveal delay={180} class="bento-watch">
<div class="glass-card feature-card watch-card">
<p class="card-title">Watch Together</p>
<!-- Avatar circles -->
<div class="avatar-row" aria-hidden="true">
<div class="avatar avatar-a">
A
</div>
<div class="avatar avatar-b">
B
</div>
<div class="avatar avatar-c">
C
</div>
<span class="sync-label">Synced</span>
</div>
</div>
</ScrollReveal>
<!-- Tracker Sync -->
<ScrollReveal delay={210} class="bento-trackers">
<div class="glass-card feature-card tracker-card">
<div class="tracker-copy">
<p class="card-title">Tracker Sync</p>
<p class="card-caption">Progress, ratings, and watch history</p>
</div>
<div class="tracker-grid" aria-label="Supported trackers">
<div class="tracker-pill trakt">
<TraktIcon />
<span>Trakt</span>
</div>
<div class="tracker-pill mal">
<MyAnimeListIcon />
<span>MAL</span>
</div>
<div class="tracker-pill anilist">
<AniListIcon />
<span>AniList</span>
</div>
<div class="tracker-pill simkl">
<SimklIcon />
<span>Simkl</span>
</div>
</div>
</div>
</ScrollReveal>
<!-- Much More — full width banner -->
<ScrollReveal delay={270} class="bento-more">
<div class="glass-card feature-card more-card">
<div>
<p class="card-title">& much more</p>
<p class="more-description">
Skip intros, chapter support, gestures, and new features added regularly.
</p>
</div>
<div class="dot-row" aria-hidden="true">
<div class="dot strong"></div>
<div class="dot medium"></div>
<div class="dot faint"></div>
</div>
</div>
</ScrollReveal>
</div>
</section>
<style>
.features-section {
max-width: 64rem;
margin-inline: auto;
padding: 4rem 1.5rem;
}
.section-label {
margin-bottom: 0.75rem;
color: var(--color-accent);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.025em;
line-height: 1.25rem;
text-transform: uppercase;
}
.section-heading {
margin-bottom: 1rem;
font-size: 2.25rem;
font-weight: 700;
line-height: 2.5rem;
}
.section-description {
max-width: 32rem;
margin-bottom: 2.5rem;
color: var(--color-text-muted);
font-size: 1.125rem;
line-height: 1.75rem;
}
.feature-card {
height: 100%;
border-radius: 1rem;
}
.codec-card,
.hdr-card,
.tracker-card {
position: relative;
overflow: hidden;
}
.codec-card,
.hdr-card,
.sub-card,
.offline-card,
.watch-card,
.tracker-card {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.codec-card {
padding: 2rem;
}
.hdr-card,
.sub-card,
.cross-card,
.offline-card,
.watch-card,
.tracker-card {
padding: 1.5rem;
}
.cross-card {
display: flex;
flex-direction: column;
}
.tracker-card {
gap: 1rem;
}
.card-content {
position: relative;
z-index: 10;
}
.card-title {
font-size: 1rem;
font-weight: 600;
line-height: 1.5rem;
}
.card-title.large {
margin-bottom: 0.5rem;
font-size: 1.25rem;
line-height: 1.75rem;
}
.card-title.with-gap {
margin-bottom: 1.25rem;
}
.card-copy {
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.625;
}
.codec-copy {
max-width: 17.5rem;
}
.card-eyebrow {
color: var(--color-text-muted);
font-size: 0.75rem;
line-height: 1rem;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.card-bottom,
.platform-list {
margin-top: auto;
}
.hdr-glow {
position: absolute;
top: -2rem;
right: -2rem;
width: 6rem;
height: 6rem;
border-radius: 9999px;
background: color-mix(in srgb, var(--color-accent) 15%, transparent);
filter: blur(40px);
pointer-events: none;
}
.hdr-title {
color: transparent;
background: linear-gradient(to right, var(--color-accent-dim), var(--color-accent));
-webkit-background-clip: text;
background-clip: text;
font-size: 1.875rem;
font-weight: 700;
line-height: 1.25;
}
.hdr-subtitle {
margin-top: 0.25rem;
color: color-mix(in srgb, var(--color-text-muted) 80%, transparent);
font-size: 0.875rem;
line-height: 1.25rem;
}
.subtitle-lines,
.progress-block {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.subtitle-line {
height: 0.375rem;
border-radius: 9999px;
}
.subtitle-line.wide {
width: 75%;
background: rgb(255 255 255 / 0.08);
}
.subtitle-line.short {
width: 50%;
background: rgb(255 255 255 / 0.05);
}
.card-caption {
margin-top: 0.25rem;
color: var(--color-text-muted);
font-size: 0.75rem;
line-height: 1rem;
}
.platform-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.platform-row {
display: flex;
align-items: center;
gap: 0.75rem;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.platform-icon-box {
display: flex;
width: 2rem;
height: 2rem;
flex-shrink: 0;
align-items: center;
justify-content: center;
border-radius: 0.5rem;
}
.platform-icon-box {
background: rgb(255 255 255 / 0.05);
}
.platform-icon-box :global(svg) {
width: 1rem;
height: 1rem;
}
.progress-block {
margin-top: 1rem;
margin-bottom: 0;
}
.progress-track {
height: 0.25rem;
overflow: hidden;
border-radius: 9999px;
background: rgb(255 255 255 / 0.1);
}
.progress-fill {
width: 75%;
height: 100%;
border-radius: 9999px;
background: color-mix(in srgb, var(--color-accent) 60%, transparent);
}
.progress-caption {
color: color-mix(in srgb, var(--color-text-muted) 60%, transparent);
font-size: 0.625rem;
line-height: 1;
}
.avatar-row {
display: flex;
align-items: center;
margin-top: 1rem;
}
.avatar {
display: flex;
width: 2rem;
height: 2rem;
align-items: center;
justify-content: center;
border: 2px solid var(--color-surface);
border-radius: 9999px;
font-size: 0.625rem;
font-weight: 700;
line-height: 1;
}
.avatar + .avatar {
margin-left: -0.5rem;
}
.avatar-a {
color: var(--color-accent);
background: color-mix(in srgb, var(--color-accent) 20%, transparent);
}
.avatar-b {
color: #60a5fa;
background: rgb(59 130 246 / 0.2);
}
.avatar-c {
color: #34d399;
background: rgb(16 185 129 / 0.2);
}
.sync-label {
margin-left: 0.5rem;
color: var(--color-text-muted);
font-size: 0.75rem;
line-height: 1rem;
}
.tracker-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.5rem;
}
.tracker-pill {
display: flex;
min-width: 0;
align-items: center;
gap: 0.375rem;
border-radius: 0.625rem;
padding: 0.5rem;
color: color-mix(in srgb, var(--color-text-muted) 90%, transparent);
background: rgb(255 255 255 / 0.05);
font-size: 0.6875rem;
font-weight: 600;
line-height: 1;
}
.tracker-pill :global(svg) {
width: 0.875rem;
height: 0.875rem;
flex-shrink: 0;
}
.tracker-pill.trakt {
color: #ed1c24;
background: color-mix(in srgb, #ed1c24 12%, transparent);
}
.tracker-pill.mal {
color: #2e51a2;
background: color-mix(in srgb, #2e51a2 18%, transparent);
}
.tracker-pill.anilist {
color: #02a9ff;
background: color-mix(in srgb, #02a9ff 14%, transparent);
}
.tracker-pill.simkl {
color: #dbdbe0;
background: color-mix(in srgb, #dbdbe0 8%, transparent);
}
.more-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem 2rem;
}
.more-description {
margin-top: 0.125rem;
color: var(--color-text-muted);
font-size: 0.75rem;
line-height: 1rem;
}
.dot-row {
display: flex;
gap: 0.375rem;
}
.dot {
width: 0.375rem;
height: 0.375rem;
border-radius: 9999px;
}
.dot.strong {
background: color-mix(in srgb, var(--color-accent) 60%, transparent);
}
.dot.medium {
background: color-mix(in srgb, var(--color-accent) 40%, transparent);
}
.dot.faint {
background: color-mix(in srgb, var(--color-accent) 20%, transparent);
}
.codec-wall {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
margin-top: 1rem;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
font-size: 0.8125rem;
font-weight: 500;
line-height: 1.8;
}
.codec-accent {
color: var(--color-accent);
}
.codec-accent-80 {
color: color-mix(in srgb, var(--color-accent) 80%, transparent);
}
.codec-accent-60 {
color: color-mix(in srgb, var(--color-accent) 60%, transparent);
}
.codec-text-70 {
color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.codec-text-50 {
color: color-mix(in srgb, var(--color-text) 50%, transparent);
}
.codec-text-40 {
color: color-mix(in srgb, var(--color-text) 40%, transparent);
}
.codec-text-30 {
color: color-mix(in srgb, var(--color-text) 30%, transparent);
}
.codec-text-20 {
color: color-mix(in srgb, var(--color-text) 20%, transparent);
}
.codec-text-15 {
color: color-mix(in srgb, var(--color-text) 15%, transparent);
}
.codec-text-10 {
color: color-mix(in srgb, var(--color-text) 10%, transparent);
}
.codec-fade {
position: absolute;
inset-inline: 0;
bottom: 0;
z-index: 1;
height: 4rem;
background: linear-gradient(to top, var(--color-surface), transparent);
pointer-events: none;
}
.bento-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-rows: auto auto auto auto;
gap: 1rem;
}
/* Named areas for precise placement — :global because classes are on child components */
.bento-grid :global(.bento-codec) {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
.bento-grid :global(.bento-hdr) {
grid-column: 3 / 4;
grid-row: 1 / 2;
}
.bento-grid :global(.bento-sub) {
grid-column: 3 / 4;
grid-row: 2 / 3;
}
.bento-grid :global(.bento-cross) {
grid-column: 4 / 5;
grid-row: 1 / 3;
}
.bento-grid :global(.bento-trackers) {
grid-column: 3 / 5;
grid-row: 3 / 4;
}
.bento-grid :global(.bento-offline) {
grid-column: 1 / 2;
grid-row: 3 / 4;
}
.bento-grid :global(.bento-watch) {
grid-column: 2 / 3;
grid-row: 3 / 4;
}
.bento-grid :global(.bento-more) {
grid-column: 1 / 5;
grid-row: 4 / 5;
}
/* Mobile: single column stack with HDR + Subtitles paired */
@media (max-width: 639px) {
.bento-grid {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto;
}
.bento-grid :global(.bento-codec),
.bento-grid :global(.bento-cross),
.bento-grid :global(.bento-offline),
.bento-grid :global(.bento-watch),
.bento-grid :global(.bento-trackers),
.bento-grid :global(.bento-more) {
grid-column: 1 / -1;
grid-row: auto;
}
.bento-grid :global(.bento-hdr) {
grid-column: 1 / 2;
grid-row: auto;
}
.bento-grid :global(.bento-sub) {
grid-column: 2 / 3;
grid-row: auto;
}
}
@media (min-width: 640px) {
.features-section {
padding-block: 6rem;
}
.section-description {
margin-bottom: 4rem;
}
.tracker-card {
flex-direction: row;
align-items: center;
}
.tracker-copy {
flex-shrink: 0;
}
.tracker-grid {
flex: 1;
}
}
@media (min-width: 768px) {
.features-section {
padding-block: 8rem;
}
.section-heading {
font-size: 3rem;
line-height: 1;
}
}
</style>
+93
View File
@@ -0,0 +1,93 @@
<script lang="ts">
import Logo from './Logo.svelte';
</script>
<footer class="site-footer">
<div class="footer-inner">
<!-- Gradient rule -->
<div class="gradient-rule"></div>
<div class="footer-content">
<!-- Logo + name -->
<div class="footer-brand">
<span class="footer-logo"><Logo /></span>
<span>Plezy</span>
</div>
<!-- Links -->
<nav class="footer-nav">
<a href="https://github.com/edde746/plezy" target="_blank" rel="noopener noreferrer">GitHub</a>
<a href="/privacy">Privacy Policy</a>
</nav>
</div>
</div>
</footer>
<style>
.site-footer {
padding: 2.5rem 1.5rem;
}
.footer-inner {
max-width: 64rem;
margin-inline: auto;
}
.gradient-rule {
height: 1px;
margin-bottom: 2rem;
background: linear-gradient(to right, transparent, var(--color-border), transparent);
}
.footer-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
gap: 2rem;
}
.footer-brand {
display: flex;
align-items: center;
gap: 0.625rem;
color: var(--color-text-muted);
font-weight: 500;
}
.footer-logo :global(svg) {
width: 1.75rem;
height: 1.75rem;
}
.footer-nav {
display: flex;
align-items: center;
gap: 2rem;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.footer-nav a {
transition: color 150ms ease;
}
.footer-nav a:hover {
color: var(--color-text);
}
@media (min-width: 640px) {
.site-footer {
padding-block: 4rem;
}
.gradient-rule {
margin-bottom: 3rem;
}
.footer-content {
flex-direction: row;
}
}
</style>
+269
View File
@@ -0,0 +1,269 @@
<script lang="ts">
import DownloadButtons from "./DownloadButtons.svelte";
import Logo from "./Logo.svelte";
import homeImage from '$lib/assets/screenshots/phone-home.png?enhanced';
import libraryImage from '$lib/assets/screenshots/phone-library.png?enhanced';
</script>
<section id="home" class="hero-section">
<!-- Giant background logo watermark -->
<div class="hero-watermark" aria-hidden="true">
<Logo gradient={false} />
</div>
<!-- Ambient glow -->
<div class="ambient-glow"></div>
<!-- Content: two-column on desktop -->
<div class="hero-content">
<!-- Left: text + buttons -->
<div class="hero-copy">
<div class="brand-row fade-in" style="animation-delay: 0ms;">
<span class="brand-logo"><Logo /></span>
<span class="brand-name">Plezy</span>
</div>
<h1
class="hero-title fade-in"
style="animation-delay: 100ms;"
>
<span class="title-line">A beautiful client for</span>
<span class="title-line gradient-text">Plex &amp; Jellyfin</span>
</h1>
<p
class="hero-description fade-in"
style="animation-delay: 200ms;"
>
Stream your media library with HDR, Dolby Vision, offline downloads, and a gorgeous experience across all your
devices.
</p>
<div class="fade-in" style="animation-delay: 300ms;">
<DownloadButtons />
</div>
</div>
<!-- Right: stacked phone mockup -->
<div class="phone-stage fade-in" style="animation-delay: 400ms;">
<div class="phone-stack">
<!-- Shadow phone behind -->
<div class="shadow-phone">
<enhanced:img
src={libraryImage}
alt=""
class="phone-image"
loading="lazy"
sizes="(min-width: 768px) 300px, (min-width: 640px) 280px, 224px"
/>
</div>
<!-- Main phone -->
<div class="main-phone">
<enhanced:img
src={homeImage}
alt="Plezy home screen"
class="phone-image"
fetchpriority="high"
sizes="(min-width: 768px) 300px, (min-width: 640px) 280px, 224px"
/>
</div>
</div>
</div>
</div>
</section>
<style>
.hero-section {
position: relative;
display: flex;
min-height: 100dvh;
align-items: center;
overflow: hidden;
}
.hero-watermark {
position: absolute;
top: 50%;
right: -5rem;
width: 37.5rem;
transform: translateY(-50%);
opacity: 0.04;
pointer-events: none;
user-select: none;
}
.hero-watermark :global(svg) {
width: 100%;
height: auto;
}
.ambient-glow {
position: absolute;
top: 33.3333%;
right: 25%;
width: 31.25rem;
height: 31.25rem;
border-radius: 9999px;
background: color-mix(in srgb, var(--color-accent) 10%, transparent);
filter: blur(120px);
animation: pulse-glow 3s ease-in-out infinite;
pointer-events: none;
}
.hero-content {
position: relative;
display: grid;
width: 100%;
max-width: 80rem;
margin-inline: auto;
padding: 4rem 1.5rem;
gap: 3rem;
align-items: center;
}
.hero-copy {
max-width: 36rem;
}
.fade-in {
animation: fade-in-up 0.6s ease-out both;
}
.brand-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.25rem;
}
.brand-logo :global(svg) {
width: 2.5rem;
height: 2.5rem;
}
.brand-name {
color: var(--color-text-muted);
font-size: 1.25rem;
font-weight: 600;
}
.hero-title {
font-size: 2.25rem;
line-height: 1.05;
font-weight: 700;
letter-spacing: -0.025em;
}
.title-line {
display: block;
white-space: nowrap;
}
.gradient-text {
color: transparent;
background: linear-gradient(to right, var(--color-accent-dim), var(--color-accent));
-webkit-background-clip: text;
background-clip: text;
}
.hero-description {
max-width: 28rem;
margin-top: 1rem;
margin-bottom: 2rem;
color: var(--color-text-muted);
font-size: 1rem;
line-height: 1.625;
}
.phone-stage {
position: relative;
display: flex;
justify-content: center;
}
.phone-stack {
position: relative;
width: 14rem;
}
.shadow-phone {
position: absolute;
top: 0.75rem;
left: -2rem;
width: 100%;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--color-border) 30%, transparent);
border-radius: 2.5rem;
opacity: 0.4;
filter: blur(1px);
transform: rotate(-4deg);
}
.main-phone {
position: relative;
overflow: hidden;
border: 2px solid color-mix(in srgb, var(--color-border) 60%, transparent);
border-radius: 2.5rem;
box-shadow: 0 0 80px -20px color-mix(in srgb, var(--color-accent) 20%, transparent);
}
.phone-image {
display: block;
width: 100%;
height: auto;
}
@media (min-width: 640px) {
.hero-content {
padding-block: 6rem;
}
.brand-row {
margin-bottom: 2rem;
}
.hero-title {
font-size: 3rem;
}
.hero-description {
margin-top: 1.5rem;
margin-bottom: 2.5rem;
font-size: 1.125rem;
}
.phone-stack {
width: 17.5rem;
}
}
@media (min-width: 768px) {
.hero-watermark {
width: 50rem;
}
.hero-title {
font-size: 3.75rem;
}
.phone-stack {
width: 18.75rem;
}
}
@media (min-width: 1024px) {
.hero-watermark {
width: 62.5rem;
}
.hero-content {
grid-template-columns: repeat(2, minmax(0, 1fr));
padding-inline: 3rem;
gap: 6rem;
}
.phone-stage {
justify-content: flex-end;
}
}
</style>
+20
View File
@@ -0,0 +1,20 @@
<script lang="ts">
let { class: className = '', gradient = true }: { class?: string; gradient?: boolean } = $props();
const gradientId = $props.id();
</script>
<svg class={className} viewBox="0 32 358.32 399.86" xmlns="http://www.w3.org/2000/svg">
{#if gradient}
<defs>
<linearGradient id={gradientId} x1="22.67" y1="425.72" x2="201.83" y2="115.4" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ab543a" />
<stop offset="1" stop-color="#ff7e57" />
</linearGradient>
</defs>
{/if}
<path
fill={gradient ? `url(#${gradientId})` : 'currentColor'}
d="M335.65,192.66L68.01,38.14C37.78,20.69,0,42.5,0,77.41v309.04c0,34.91,37.78,56.72,68.01,39.27l267.64-154.52c30.23-17.45,30.23-61.08,0-78.54ZM255.53,276.8c-19.1,17.66-38.75,26.49-58.4,26.49s-39.29-8.83-58.39-26.49c-14.39-13.3-28.55-20.05-42.11-20.05s-27.72,6.75-42.1,20.05c-4.87,4.5-12.46,4.2-16.96-.67-4.5-4.86-4.2-12.45.67-16.95,38.2-35.33,78.59-35.33,116.79,0,14.38,13.3,28.55,20.04,42.1,20.04s27.72-6.74,42.11-20.04c4.86-4.5,12.46-4.21,16.95.66,4.5,4.87,4.21,12.46-.66,16.96ZM255.53,204.04c-19.1,17.67-38.75,26.5-58.4,26.5s-39.29-8.83-58.39-26.5c-14.39-13.3-28.55-20.04-42.11-20.04s-27.72,6.74-42.1,20.04c-4.87,4.5-12.46,4.21-16.96-.66s-4.2-12.46.67-16.96c38.2-35.32,78.59-35.32,116.79,0,14.38,13.3,28.55,20.05,42.1,20.05s27.72-6.75,42.11-20.05c4.86-4.5,12.46-4.2,16.95.67,4.5,4.86,4.21,12.45-.66,16.95Z"
/>
</svg>
@@ -0,0 +1 @@
<div class="noise-overlay" aria-hidden="true"></div>
+463
View File
@@ -0,0 +1,463 @@
<script lang="ts">
import { onMount } from "svelte";
import ScrollReveal from "./ScrollReveal.svelte";
import AppleIcon from "~icons/simple-icons/apple";
import GooglePlayIcon from "~icons/simple-icons/googleplay";
import RedditIcon from "~icons/fa6-brands/reddit-alien";
import ArrowUpIcon from "~icons/heroicons/arrow-up-solid";
import StarIcon from "~icons/heroicons/star-solid";
type SourceIcon = typeof AppleIcon | typeof GooglePlayIcon | typeof RedditIcon;
type Review = {
text: string;
name: string;
source: "App Store" | "Play Store" | "Reddit";
icon: SourceIcon;
upvotes?: number;
};
const row1: Review[] = [
{
text: "Impressively way better than the official Plex client!",
name: "Adam P.",
source: "Play Store",
icon: GooglePlayIcon,
},
{
text: "Really works better than the native plex app. If the developer keeps going this can be a killer app",
name: "Mr. Wick 03",
source: "App Store",
icon: AppleIcon,
},
{
text: "Holy shit. This comment just made me install it and give it a shot. I've never had a video open so fast. It was amazing.",
name: "u/Khatib",
source: "Reddit",
icon: RedditIcon,
upvotes: 69,
},
{
text: "Clean UI, great performance, and things ACTUALLY WORK. Scrubbing through a video takes no time at all, turning on subtitles takes maybe .5s, starting episodes is near instant. Incredible work.",
name: "Frank the Tank uwu",
source: "App Store",
icon: AppleIcon,
},
{
text: "Downloads that work...what a concept.",
name: "Chris R.",
source: "Play Store",
icon: GooglePlayIcon,
},
{
text: "It's leap years ahead of Plex's slow and horrible UI. Fast, responsive, and just works. 10/10",
name: "Starry_mcfly",
source: "App Store",
icon: AppleIcon,
},
{
text: "Best plex client I know of, so much better than official Plex app.",
name: "James K.",
source: "Play Store",
icon: GooglePlayIcon,
},
{
text: "This app is absolutely beautiful. It's clean and smooth. A real professional feel to it. Much better than the official Plex app.",
name: "Mr Kinxy",
source: "App Store",
icon: AppleIcon,
},
];
const row2: Review[] = [
{
text: "Replaced the official Plex client with your app and WOW! Even Infuse didn't feel this smooth while also looking nice! Great work!!",
name: "u/mustbeSaransh",
source: "Reddit",
icon: RedditIcon,
upvotes: 50,
},
{
text: "If you're having troubles with the default plex app just ditch it and switch to this. Plezy is faster, and easier to use. Worth the $5",
name: "boyknight53",
source: "App Store",
icon: AppleIcon,
},
{
text: "Better than the official client after just a few months. Keeps getting updates that make it better. Great app.",
name: "Derek S.",
source: "Play Store",
icon: GooglePlayIcon,
},
{
text: "Nice, fast and clean app! It has much greater support for codecs than the official app and the download function is made better. I'm changing to this now.",
name: "Marcus L.",
source: "Play Store",
icon: GooglePlayIcon,
},
{
text: "College student here this is a godsend. Cannot recommend enough. For mediaheads on the go this is the best $4 you could ever spend.",
name: "djmeowmeow",
source: "App Store",
icon: AppleIcon,
},
{
text: "Much better than the official app! The sticking point for me is that downloads actually work reliably. The interface is great and snappy.",
name: "Ryan T.",
source: "Play Store",
icon: GooglePlayIcon,
},
{
text: "This has to be my new favorite plex client. Starts up and connects quickly. Quality is amazing. Well worth the $5.",
name: "nasukaren",
source: "App Store",
icon: AppleIcon,
},
{
text: "Fantastic and super lean looking! Must have if you have a Plex server.",
name: "Alex W.",
source: "Play Store",
icon: GooglePlayIcon,
},
];
let mounted = $state(false);
onMount(() => {
mounted = true;
});
</script>
<section id="reviews" class="reviews-section">
<div class="reviews-header">
<ScrollReveal>
<p class="section-label">Reviews</p>
<h2 class="section-heading">Loved by users</h2>
<p class="section-description">
See what people are saying about their experience with Plezy.
</p>
</ScrollReveal>
</div>
<!-- Row 1: scrolls left -->
<div class="marquee-row row-gap">
<div class="marquee-track marquee-left">
{#each row1 as review}
{@const SourceIcon = review.icon}
<div class="review-card glass-card">
{#if review.source === "Reddit"}
<div class="upvote">
<ArrowUpIcon />
<span>{review.upvotes}</span>
</div>
{:else}
<div class="stars">
{#each Array(5) as _}
<StarIcon />
{/each}
</div>
{/if}
<p class="review-text">"{review.text}"</p>
<div class="review-footer">
<span class="review-name">{review.name}</span>
<div class="review-source">
<SourceIcon />
<span>{review.source}</span>
</div>
</div>
</div>
{/each}
{#if mounted}
<!-- Duplicate for seamless loop -->
<div aria-hidden="true" class="duplicate-cards">
{#each row1 as review}
{@const SourceIcon = review.icon}
<div class="review-card glass-card">
{#if review.source === "Reddit"}
<div class="upvote">
<ArrowUpIcon />
<span>{review.upvotes}</span>
</div>
{:else}
<div class="stars">
{#each Array(5) as _}
<StarIcon />
{/each}
</div>
{/if}
<p class="review-text">"{review.text}"</p>
<div class="review-footer">
<span class="review-name">{review.name}</span>
<div class="review-source">
<SourceIcon />
<span>{review.source}</span>
</div>
</div>
</div>
{/each}
</div>
{/if}
</div>
</div>
<!-- Row 2: scrolls right -->
<div class="marquee-row">
<div class="marquee-track marquee-right">
{#each row2 as review}
{@const SourceIcon = review.icon}
<div class="review-card glass-card">
{#if review.source === "Reddit"}
<div class="upvote">
<ArrowUpIcon />
<span>{review.upvotes}</span>
</div>
{:else}
<div class="stars">
{#each Array(5) as _}
<StarIcon />
{/each}
</div>
{/if}
<p class="review-text">"{review.text}"</p>
<div class="review-footer">
<span class="review-name">{review.name}</span>
<div class="review-source">
<SourceIcon />
<span>{review.source}</span>
</div>
</div>
</div>
{/each}
{#if mounted}
<!-- Duplicate for seamless loop -->
<div aria-hidden="true" class="duplicate-cards">
{#each row2 as review}
{@const SourceIcon = review.icon}
<div class="review-card glass-card">
{#if review.source === "Reddit"}
<div class="upvote">
<ArrowUpIcon />
<span>{review.upvotes}</span>
</div>
{:else}
<div class="stars">
{#each Array(5) as _}
<StarIcon />
{/each}
</div>
{/if}
<p class="review-text">"{review.text}"</p>
<div class="review-footer">
<span class="review-name">{review.name}</span>
<div class="review-source">
<SourceIcon />
<span>{review.source}</span>
</div>
</div>
</div>
{/each}
</div>
{/if}
</div>
</div>
</section>
<style>
.reviews-section {
overflow: hidden;
padding-block: 4rem;
}
.reviews-header {
max-width: 64rem;
margin-inline: auto;
margin-bottom: 2.5rem;
padding-inline: 1.5rem;
}
.section-label {
margin-bottom: 0.75rem;
color: var(--color-accent);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.025em;
line-height: 1.25rem;
text-transform: uppercase;
}
.section-heading {
margin-bottom: 1rem;
font-size: 2.25rem;
font-weight: 700;
line-height: 2.5rem;
}
.section-description {
max-width: 32rem;
color: var(--color-text-muted);
font-size: 1.125rem;
line-height: 1.75rem;
}
.row-gap {
margin-bottom: 1rem;
}
.review-card {
display: flex;
width: 300px;
flex-shrink: 0;
flex-direction: column;
padding: 1.25rem;
border-radius: 1rem;
}
.upvote,
.stars,
.review-footer,
.review-source {
display: flex;
align-items: center;
}
.upvote {
gap: 0.375rem;
margin-bottom: 0.75rem;
color: var(--color-accent);
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
}
.upvote :global(svg) {
width: 1rem;
height: 1rem;
}
.stars {
gap: 0.125rem;
margin-bottom: 0.75rem;
color: var(--color-accent);
}
.stars :global(svg),
.review-source :global(svg) {
width: 0.875rem;
height: 0.875rem;
}
.review-text {
flex: 1;
margin-bottom: 1rem;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.625;
}
.review-footer {
justify-content: space-between;
}
.review-name {
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25rem;
}
.review-source {
gap: 0.375rem;
color: color-mix(in srgb, var(--color-text-muted) 60%, transparent);
font-size: 0.75rem;
line-height: 1rem;
}
.duplicate-cards {
display: contents;
}
.marquee-row {
overflow: hidden;
mask-image: linear-gradient(
to right,
transparent,
black 5%,
black 95%,
transparent
);
-webkit-mask-image: linear-gradient(
to right,
transparent,
black 5%,
black 95%,
transparent
);
}
.marquee-track {
display: flex;
gap: 1rem;
width: max-content;
will-change: transform;
}
.marquee-left {
animation: scroll-left 60s linear infinite;
}
.marquee-right {
animation: scroll-right 60s linear infinite;
}
.marquee-row:hover .marquee-track {
animation-play-state: paused;
}
@keyframes scroll-left {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@keyframes scroll-right {
from {
transform: translateX(-50%);
}
to {
transform: translateX(0);
}
}
@media (prefers-reduced-motion: reduce) {
.marquee-track {
animation-play-state: paused;
}
}
@media (min-width: 640px) {
.reviews-section {
padding-block: 6rem;
}
.reviews-header {
margin-bottom: 4rem;
}
.review-card {
width: 340px;
}
}
@media (min-width: 768px) {
.reviews-section {
padding-block: 8rem;
}
.section-heading {
font-size: 3rem;
line-height: 1;
}
}
</style>
@@ -0,0 +1,481 @@
<script lang="ts">
import ScrollReveal from './ScrollReveal.svelte';
import DevicePhoneIcon from '~icons/heroicons/device-phone-mobile-solid';
import DeviceTabletIcon from '~icons/heroicons/device-tablet-solid';
import DesktopIcon from '~icons/heroicons/computer-desktop-solid';
import TvIcon from '~icons/heroicons/tv-solid';
import ChevronLeftIcon from '~icons/heroicons/chevron-left-solid';
import ChevronRightIcon from '~icons/heroicons/chevron-right-solid';
import phoneHomeImage from '$lib/assets/screenshots/phone-home.png?enhanced';
import phoneLibraryImage from '$lib/assets/screenshots/phone-library.png?enhanced';
import phoneMdImage from '$lib/assets/screenshots/phone-md.png?enhanced';
import phoneSearchImage from '$lib/assets/screenshots/phone-search.png?enhanced';
import tabletHomeImage from '$lib/assets/screenshots/tablet-home.png?enhanced';
import tabletLibraryImage from '$lib/assets/screenshots/tablet-library.png?enhanced';
import tabletMdImage from '$lib/assets/screenshots/tablet-md.png?enhanced';
import tabletPlayerImage from '$lib/assets/screenshots/tablet-player.png?enhanced';
import desktopHomeImage from '$lib/assets/screenshots/desktop-home.png?enhanced';
import desktopLibraryImage from '$lib/assets/screenshots/desktop-library.png?enhanced';
import desktopMdImage from '$lib/assets/screenshots/desktop-md.png?enhanced';
import desktopPlayerImage from '$lib/assets/screenshots/desktop-player.png?enhanced';
import tvHomeImage from '$lib/assets/screenshots/tv-home.png?enhanced';
import tvLibraryImage from '$lib/assets/screenshots/tv-library.png?enhanced';
import tvMdImage from '$lib/assets/screenshots/tv-md.png?enhanced';
import tvPlayerImage from '$lib/assets/screenshots/tv-player.png?enhanced';
type DeviceType = 'phone' | 'tablet' | 'desktop' | 'tv';
type DeviceIconComponent = typeof DevicePhoneIcon | typeof DeviceTabletIcon | typeof DesktopIcon | typeof TvIcon;
const devices: { id: DeviceType; icon: DeviceIconComponent; label: string }[] = [
{ id: 'phone', icon: DevicePhoneIcon, label: 'Phone' },
{ id: 'tablet', icon: DeviceTabletIcon, label: 'Tablet' },
{ id: 'desktop', icon: DesktopIcon, label: 'Desktop' },
{ id: 'tv', icon: TvIcon, label: 'TV' },
];
const phoneShots = [
{ image: phoneHomeImage, alt: 'Plezy home screen' },
{ image: phoneLibraryImage, alt: 'Plezy library view' },
{ image: phoneMdImage, alt: 'Plezy media details' },
{ image: phoneSearchImage, alt: 'Plezy search' },
];
const tabletShots = [
{ image: tabletHomeImage, alt: 'Plezy on tablet - home' },
{ image: tabletLibraryImage, alt: 'Plezy on tablet - library' },
{ image: tabletMdImage, alt: 'Plezy on tablet - media details' },
{ image: tabletPlayerImage, alt: 'Plezy on tablet - video player' },
];
const desktopShots = [
{ image: desktopHomeImage, alt: 'Plezy on desktop - home' },
{ image: desktopLibraryImage, alt: 'Plezy on desktop - library' },
{ image: desktopMdImage, alt: 'Plezy on desktop - media details' },
{ image: desktopPlayerImage, alt: 'Plezy on desktop - video player' },
];
const tvShots = [
{ image: tvHomeImage, alt: 'Plezy on TV - home' },
{ image: tvLibraryImage, alt: 'Plezy on TV - library' },
{ image: tvMdImage, alt: 'Plezy on TV - media details' },
{ image: tvPlayerImage, alt: 'Plezy on TV - video player' },
];
const screenshots: Record<
DeviceType,
{
shots: typeof phoneShots;
frameClass: string;
sizes: string;
ariaLabel: string;
}
> = {
phone: {
shots: phoneShots,
frameClass: 'phone-frame',
sizes: '(min-width: 1024px) 214px, 187px',
ariaLabel: 'Phone screenshots',
},
tablet: {
shots: tabletShots,
frameClass: 'tablet-frame',
sizes: '(min-width: 1024px) 768px, 672px',
ariaLabel: 'Tablet screenshots',
},
desktop: {
shots: desktopShots,
frameClass: 'desktop-frame',
sizes: '(min-width: 1024px) 768px, 672px',
ariaLabel: 'Desktop screenshots',
},
tv: {
shots: tvShots,
frameClass: 'tv-frame',
sizes: '(min-width: 1024px) 854px, 747px',
ariaLabel: 'TV screenshots',
},
};
let active: DeviceType = $state('phone');
let scrollContainer: HTMLElement | undefined = $state();
let canScrollLeft = $state(false);
let canScrollRight = $state(false);
let intendedScrollLeft: number | undefined;
function updateScrollState() {
if (!scrollContainer) return;
if (intendedScrollLeft !== undefined && Math.abs(scrollContainer.scrollLeft - intendedScrollLeft) < 2) {
intendedScrollLeft = undefined;
}
canScrollLeft = scrollContainer.scrollLeft > 10;
canScrollRight = scrollContainer.scrollLeft < scrollContainer.scrollWidth - scrollContainer.clientWidth - 10;
}
function scroll(dir: 'left' | 'right') {
if (!scrollContainer) return;
const items = Array.from(scrollContainer.querySelectorAll<HTMLElement>('.screenshot-item'));
const maxScroll = scrollContainer.scrollWidth - scrollContainer.clientWidth;
if (!items.length || maxScroll <= 0) return;
const tolerance = 2;
const current = scrollContainer.scrollLeft;
const base = intendedScrollLeft ?? current;
const containerLeft = scrollContainer.getBoundingClientRect().left;
const paddingLeft = parseFloat(getComputedStyle(scrollContainer).paddingLeft) || 0;
const targets = items.map((item) => item.getBoundingClientRect().left - containerLeft + current - paddingLeft);
const target = dir === 'right'
? Math.min(targets.find((left) => left > base + tolerance) ?? maxScroll, maxScroll)
: targets.filter((left) => left < base - tolerance && left <= maxScroll + tolerance).at(-1) ?? 0;
intendedScrollLeft = target;
canScrollLeft = target > 10;
canScrollRight = target < maxScroll - 10;
scrollContainer.scrollTo({ left: target, behavior: 'smooth' });
}
$effect(() => {
// Re-check scroll state when active tab changes.
const currentActive = active;
const el = document.getElementById(`screenshots-${currentActive}-panel`);
intendedScrollLeft = undefined;
scrollContainer = el ?? undefined;
if (el) {
// Double rAF ensures browser has computed layout after DOM update
const raf = requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (currentActive === active && el === scrollContainer) updateScrollState();
});
});
return () => cancelAnimationFrame(raf);
} else {
canScrollLeft = false;
canScrollRight = false;
}
});
</script>
<section id="screenshots" class="screenshots-section">
<div class="screenshots-header">
<ScrollReveal>
<p class="section-label">Preview</p>
<h2 class="section-heading">Designed with care</h2>
<p class="section-description">An experience that feels right at home on every device.</p>
<div class="screenshot-controls">
<!-- Device tabs -->
<div class="device-tabs" role="group" aria-label="Screenshot device">
{#each devices as device}
{@const DeviceIcon = device.icon}
<button
type="button"
onclick={() => active = device.id}
aria-pressed={active === device.id}
aria-controls={`screenshots-${device.id}-panel`}
aria-label={`Show ${device.label} screenshots`}
class="device-button"
class:active={active === device.id}
>
<DeviceIcon />
<span class="device-label">{device.label}</span>
</button>
{/each}
</div>
<!-- Scroll arrows -->
<div class="scroll-arrows">
<button
type="button"
aria-label="Scroll screenshots left"
onclick={() => scroll('left')}
disabled={!canScrollLeft}
class="scroll-arrow"
class:enabled={canScrollLeft}
>
<ChevronLeftIcon />
</button>
<button
type="button"
aria-label="Scroll screenshots right"
onclick={() => scroll('right')}
disabled={!canScrollRight}
class="scroll-arrow"
class:enabled={canScrollRight}
>
<ChevronRightIcon />
</button>
</div>
</div>
</ScrollReveal>
</div>
<div class="screenshot-panels">
{#each devices as device (device.id)}
{@const screenshot = screenshots[device.id]}
<div
id={`screenshots-${device.id}-panel`}
role="region"
aria-label={screenshot.ariaLabel}
aria-hidden={active !== device.id}
class="screenshot-strip scrollbar-hide content-pad"
class:panel-active={active === device.id}
onscroll={() => {
if (active === device.id) updateScrollState();
}}
>
{#each screenshot.shots as shot}
<div class="screenshot-item">
<div class={`screenshot-frame ${screenshot.frameClass}`}>
<enhanced:img
src={shot.image}
alt={shot.alt}
loading="eager"
class="screenshot-image"
sizes={screenshot.sizes}
/>
</div>
</div>
{/each}
</div>
{/each}
</div>
</section>
<style>
.screenshots-section {
overflow: hidden;
padding-block: 4rem;
}
.screenshots-header {
max-width: 64rem;
margin-inline: auto;
margin-bottom: 2rem;
padding-inline: 1.5rem;
}
.section-label {
margin-bottom: 0.75rem;
color: var(--color-accent);
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.025em;
line-height: 1.25rem;
text-transform: uppercase;
}
.section-heading {
margin-bottom: 1rem;
font-size: 2.25rem;
font-weight: 700;
line-height: 2.5rem;
}
.section-description {
max-width: 32rem;
margin-bottom: 1.5rem;
color: var(--color-text-muted);
font-size: 1.125rem;
line-height: 1.75rem;
}
.screenshot-controls {
display: flex;
align-items: center;
gap: 1rem;
}
.device-tabs {
display: flex;
width: fit-content;
gap: 0.25rem;
border-radius: 0.75rem;
background: rgb(255 255 255 / 0.04);
padding: 0.25rem;
}
.device-button {
display: flex;
align-items: center;
gap: 0.5rem;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
color: var(--color-text-muted);
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
transition: background-color 150ms ease, color 150ms ease;
}
.device-button:not(.active):hover {
color: color-mix(in srgb, var(--color-text) 80%, transparent);
}
.device-button.active {
color: var(--color-text);
background: rgb(255 255 255 / 0.1);
}
.device-button :global(svg),
.scroll-arrow :global(svg) {
width: 1rem;
height: 1rem;
}
.device-label {
display: none;
}
.scroll-arrows {
display: none;
align-items: center;
gap: 0.25rem;
margin-left: auto;
}
.scroll-arrow {
display: flex;
width: 2rem;
height: 2rem;
align-items: center;
justify-content: center;
border-radius: 0.5rem;
background: rgb(255 255 255 / 0.04);
color: color-mix(in srgb, var(--color-text-muted) 30%, transparent);
transition: background-color 150ms ease, color 150ms ease;
}
.scroll-arrow.enabled {
color: var(--color-text);
}
.scroll-arrow.enabled:hover {
background: rgb(255 255 255 / 0.1);
}
.screenshot-panels {
position: relative;
min-height: calc(420px + 1rem);
}
.screenshot-strip {
position: absolute;
top: 0;
left: 0;
display: flex;
width: 100%;
gap: 1.25rem;
overflow-x: auto;
opacity: 0;
padding-bottom: 1rem;
pointer-events: none;
transition: opacity 220ms ease;
}
.screenshot-strip.panel-active {
position: relative;
z-index: 1;
opacity: 1;
pointer-events: auto;
}
.screenshot-item {
height: 420px;
flex-shrink: 0;
scroll-snap-align: start;
}
.screenshot-frame {
height: 100%;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--color-border) 40%, transparent);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);
}
.phone-frame {
border-radius: 2rem;
}
.tablet-frame {
border-radius: 1rem;
}
.desktop-frame,
.tv-frame {
border-radius: 0.75rem;
}
.tv-frame {
border-width: 2px;
border-color: color-mix(in srgb, var(--color-border) 30%, transparent);
background: #000;
}
.screenshot-image {
display: block;
width: auto;
height: 100%;
}
.content-pad {
/* Left padding aligns with max-w-5xl (64rem) + px-6 */
padding-left: max(1.5rem, calc((100vw - 64rem) / 2 + 1.5rem));
padding-right: 1.5rem;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
@media (min-width: 640px) {
.screenshots-section {
padding-block: 6rem;
}
.screenshots-header {
margin-bottom: 3rem;
}
.section-description {
margin-bottom: 2.5rem;
}
.device-label {
display: inline;
}
}
@media (min-width: 768px) {
.screenshots-section {
padding-block: 8rem;
}
.section-heading {
font-size: 3rem;
line-height: 1;
}
.scroll-arrows {
display: flex;
}
}
@media (min-width: 1024px) {
.screenshot-panels {
min-height: calc(480px + 1rem);
}
.screenshot-item {
height: 480px;
}
}
</style>
@@ -0,0 +1,39 @@
<script lang="ts">
import type { Snippet } from 'svelte';
let { children, delay = 0, class: className = '' }: { children: Snippet; delay?: number; class?: string } = $props();
let el: HTMLDivElement | undefined = $state();
let visible = $state(false);
$effect(() => {
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
visible = true;
observer.disconnect();
}
},
{ threshold: 0.1 }
);
observer.observe(el);
return () => observer.disconnect();
});
</script>
<div
bind:this={el}
class="{className} scroll-reveal"
style="opacity: {visible ? 1 : 0}; transform: translateY({visible ? 0 : 24}px); transition-delay: {delay}ms;"
>
{@render children()}
</div>
<style>
.scroll-reveal {
transition-property: opacity, transform;
transition-duration: 600ms;
transition-timing-function: ease-out;
}
</style>
@@ -0,0 +1,7 @@
<script lang="ts">
let { class: className = '' }: { class?: string } = $props();
</script>
<svg class={className} viewBox="0 0 448 512" fill="currentColor">
<path d="M0 93.7l183.6-25.3v177.4H0V93.7zm0 324.6l183.6 25.3V268.4H0v149.9zm203.8 28L448 480V268.4H203.8v177.9zm0-380.6v180.1H448V32L203.8 65.7z" />
</svg>
+75
View File
@@ -0,0 +1,75 @@
export type Faq = {
id: string;
question: string;
answer: string;
schemaAnswer?: string;
};
export const faqs: Faq[] = [
{
id: "plex-pass",
question: "Do I need Plex Pass to stream remotely?",
answer:
"No. The remote viewing checks are done client-side in the official Plex apps, not on your server. Plezy connects directly to your server's API, so there are no such checks.",
},
{
id: "jellyfin",
question: "Does Plezy support Jellyfin?",
answer:
"Yes! Plezy works with Jellyfin servers alongside Plex. You can sign in with either your Jellyfin username and password or use Quick Connect for a one-tap login from another device. Most of Plezy's features — direct play, HDR, subtitles, offline downloads — work the same way on both.",
},
{
id: "free",
question: "Is Plezy free?",
answer:
"Plezy is open-source and free to download from GitHub. The App Store and Play Store versions are paid (one-time purchase, price varies by region). The charge helps cover developer account fees, test devices, and the time spent developing and maintaining the app.<br><br>There's no free trial because the app stores don't natively support trials for paid apps. The only way to offer one would be to make the app free and use in-app purchases — which means integrating StoreKit and Google Play Billing, gating features behind a paywall, and adding a layer of complexity that doesn't make sense for a simple one-time purchase. If you'd like to try before you buy, you can sideload the binaries from GitHub or try it on desktop first.",
},
{
id: "apple-tv",
question: "Does Plezy work on Apple TV?",
answer: "Yes, Plezy is available for tvOS on the App Store.",
},
{
id: "android-tv",
question: "Does Plezy work on Android TV / Shield?",
answer: "Yes! The app is available for Android TV including the Shield.",
},
{
id: "watch-together",
question: "How does Watch Together work?",
answer:
"Watch Together uses a WebSocket relay to sync playback between users. The other person needs access to the same media on the same server. Only playback sync messages are exchanged - nothing about your server is shared.",
},
{
id: "video-player",
question: "What video player does Plezy use?",
answer:
'mpv on most platforms, with ExoPlayer available on Android for HDR support and better performance. ExoPlayer also has libass support via <a href="https://github.com/peerless2012/libass-android" target="_blank" rel="noopener">libass-android</a>.',
schemaAnswer:
"mpv on most platforms, with ExoPlayer available on Android for HDR support and better performance. ExoPlayer also has libass support via libass-android.",
},
{
id: "plex-block",
question: "Will Plex block this app?",
answer: "Unlikely. We use the officially documented Plex API with their recommended authentication method.",
},
];
function htmlToText(value: string) {
return value
.replace(/<br\s*\/?>/gi, " ")
.replace(/<[^>]+>/g, "")
.replace(/&amp;/g, "&")
.replace(/&middot;/g, "·")
.replace(/\s+/g, " ")
.trim();
}
export const faqSchemaMainEntity = faqs.map((faq) => ({
"@type": "Question",
name: faq.question,
acceptedAnswer: {
"@type": "Answer",
text: faq.schemaAnswer ?? htmlToText(faq.answer),
},
}));
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+82
View File
@@ -0,0 +1,82 @@
<script lang="ts">
import { page } from '$app/state';
import Logo from '$lib/components/Logo.svelte';
</script>
<svelte:head>
<title>{page.status} - Plezy</title>
<meta name="robots" content="noindex" />
</svelte:head>
<div class="error-page">
<div class="error-card">
<span class="error-logo"><Logo /></span>
<h1 class="error-code">{page.status}</h1>
<p class="error-message">{page.error?.message || 'Page not found'}</p>
<a
href="/"
class="home-link"
>
Back to home
</a>
</div>
</div>
<style>
.error-page {
display: flex;
min-height: 100vh;
align-items: center;
justify-content: center;
padding: 4rem 1.5rem;
}
.error-card {
display: flex;
max-width: 24rem;
flex-direction: column;
align-items: center;
text-align: center;
}
.error-logo {
margin-bottom: 1.5rem;
}
.error-logo :global(svg) {
width: 4rem;
height: 4rem;
}
.error-code {
margin-bottom: 0.5rem;
font-size: 3rem;
font-weight: 700;
line-height: 1;
}
.error-message {
margin-bottom: 2rem;
color: var(--color-text-muted);
}
.home-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.25rem;
border-radius: 9999px;
background: var(--color-accent);
color: #fff;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25rem;
transition: background-color 150ms ease;
}
.home-link:hover {
background: color-mix(in srgb, var(--color-accent) 90%, transparent);
}
</style>
+29
View File
@@ -0,0 +1,29 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
let { children } = $props();
const siteSchema = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Plezy",
"url": "https://plezy.app"
};
const orgSchema = {
"@context": "https://schema.org",
"@type": "Organization",
"name": "Plezy",
"url": "https://plezy.app",
"logo": "https://plezy.app/og/plezy-social.png",
"sameAs": ["https://github.com/edde746/plezy"]
};
</script>
<svelte:head>
<link rel="icon" href={favicon} />
{@html `<script type="application/ld+json">${JSON.stringify(siteSchema)}</script>`}
{@html `<script type="application/ld+json">${JSON.stringify(orgSchema)}</script>`}
</svelte:head>
{@render children()}
+1
View File
@@ -0,0 +1 @@
export const prerender = true;
+58
View File
@@ -0,0 +1,58 @@
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ fetch }) => {
let appStoreRating: { score: number; count: number } | null = null;
let playStoreRating: { score: number; count: number } | null = null;
let appStorePrice: string | null = null;
let playStorePrice: string | null = null;
try {
const res = await fetch('https://itunes.apple.com/lookup?id=6754315964');
const data = await res.json();
const app = data.results?.[0];
if (app?.averageUserRating && app?.userRatingCount) {
appStoreRating = {
score: app.averageUserRating,
count: app.userRatingCount
};
}
if (app?.price != null) {
appStorePrice = String(app.price);
}
} catch {
// App Store fetch failed, continue without it
}
try {
const gplay = await import('google-play-scraper');
const app = await gplay.default.app({ appId: 'com.edde746.plezy' });
if (app.score && app.ratings) {
playStoreRating = {
score: app.score,
count: app.ratings
};
}
if (app.price != null) {
playStorePrice = String(app.price);
}
} catch {
// Play Store fetch failed, continue without it
}
// Compute combined weighted average
let aggregateRating: { ratingValue: string; ratingCount: number } | null = null;
const ratings = [appStoreRating, playStoreRating].filter(Boolean) as {
score: number;
count: number;
}[];
if (ratings.length > 0) {
const totalCount = ratings.reduce((sum, r) => sum + r.count, 0);
const weightedSum = ratings.reduce((sum, r) => sum + r.score * r.count, 0);
aggregateRating = {
ratingValue: (weightedSum / totalCount).toFixed(1),
ratingCount: totalCount
};
}
return { aggregateRating, appStorePrice, playStorePrice };
};
+104
View File
@@ -0,0 +1,104 @@
<script lang="ts">
import Hero from '$lib/components/Hero.svelte';
import Features from '$lib/components/Features.svelte';
import Screenshots from '$lib/components/Screenshots.svelte';
import Reviews from '$lib/components/Reviews.svelte';
import FAQ from '$lib/components/FAQ.svelte';
import Footer from '$lib/components/Footer.svelte';
import NoiseOverlay from '$lib/components/NoiseOverlay.svelte';
import { faqSchemaMainEntity } from '$lib/content/faqs';
const { data } = $props();
const title = "Plezy - A Beautiful Plex & Jellyfin Client";
const description = "Plezy is a beautiful client for Plex and Jellyfin, available on iOS, Android, Android TV, tvOS, Windows, macOS, and Linux. HDR, Dolby Vision, offline downloads, and more.";
const url = "https://plezy.app/";
const image = "https://plezy.app/og/plezy-social.png";
const softwareAppSchema = $derived.by(() => {
const schema: Record<string, unknown> = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Plezy",
"description": description,
"url": "https://plezy.app",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "iOS, Android, Android TV, tvOS, Windows, macOS, Linux",
"offers": [
{
"@type": "Offer",
"url": "https://apps.apple.com/us/app/id6754315964",
"price": data.appStorePrice ?? "0",
"priceCurrency": "USD",
"category": "App Store"
},
{
"@type": "Offer",
"url": "https://play.google.com/store/apps/details?id=com.edde746.plezy",
"price": data.playStorePrice ?? "0",
"priceCurrency": "USD",
"category": "Google Play"
},
{
"@type": "Offer",
"url": "https://www.amazon.com/gp/product/B0GK65CVS1",
"category": "Amazon Appstore"
},
{
"@type": "Offer",
"url": "https://github.com/edde746/plezy",
"price": "0",
"priceCurrency": "USD",
"category": "GitHub"
}
]
};
if (data.aggregateRating) {
schema.aggregateRating = {
"@type": "AggregateRating",
"ratingValue": data.aggregateRating.ratingValue,
"ratingCount": data.aggregateRating.ratingCount,
"bestRating": "5",
"worstRating": "1"
};
}
return schema;
});
const faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqSchemaMainEntity
};
</script>
<svelte:head>
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={url} />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Plezy" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={url} />
<meta property="og:image" content={image} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={image} />
{@html `<script type="application/ld+json">${JSON.stringify(softwareAppSchema)}</script>`}
{@html `<script type="application/ld+json">${JSON.stringify(faqSchema)}</script>`}
</svelte:head>
<NoiseOverlay />
<Hero />
<Features />
<Screenshots />
<Reviews />
<FAQ />
<Footer />
+180
View File
@@ -0,0 +1,180 @@
:root {
--color-bg: #0e0f12;
--color-surface: #15171c;
--color-surface-hover: #1a1c22;
--color-border: #2a2d35;
--color-accent: #ff6b45;
--color-accent-glow: #ff6b4533;
--color-accent-dim: #ab543a;
--color-text: #ededed;
--color-text-muted: #9ca3af;
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
--animate-fade-in-up: fade-in-up 0.6s ease-out both;
--animate-pulse-glow: pulse-glow 3s ease-in-out infinite;
--animate-float: float 6s ease-in-out infinite;
}
*,
*::before,
*::after {
box-sizing: border-box;
border: 0 solid;
}
html,
body,
h1,
h2,
h3,
p,
ul {
margin: 0;
}
html {
scroll-behavior: smooth;
background-color: var(--color-bg);
color: var(--color-text);
}
body {
min-width: 320px;
min-height: 100vh;
font-family: var(--font-sans);
line-height: 1.5;
background-color: var(--color-bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
color: inherit;
text-decoration: inherit;
}
button,
input,
textarea,
select {
font: inherit;
color: inherit;
}
button {
padding: 0;
background: transparent;
cursor: pointer;
}
button:disabled {
cursor: default;
}
img,
svg,
picture,
video,
canvas {
display: block;
}
img,
picture,
video,
canvas {
max-width: 100%;
}
img,
video {
height: auto;
}
ul {
padding: 0;
list-style: none;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse-glow {
0%, 100% {
opacity: 0.6;
filter: blur(60px);
}
50% {
opacity: 1;
filter: blur(80px);
}
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-12px);
}
}
@keyframes gradient-shift {
0%, 100% {
transform: translate(0, 0) scale(1);
}
33% {
transform: translate(30px, -20px) scale(1.1);
}
66% {
transform: translate(-20px, 15px) scale(0.95);
}
}
/* Glassmorphism card utility */
.glass-card {
background: color-mix(in srgb, var(--color-surface) 60%, transparent);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--color-border);
transition: box-shadow 0.3s ease, border-color 0.3s ease;
}
.glass-card:hover {
border-color: var(--color-accent-glow);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 0 0 1px var(--color-accent-glow),
0 0 40px -12px var(--color-accent-glow);
}
/* Noise overlay texture */
.noise-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 50;
opacity: 0.035;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 256px 256px;
}
/* Scroll indicator */
@keyframes scroll-bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(8px);
}
}
+257
View File
@@ -0,0 +1,257 @@
<script lang="ts">
import Logo from '$lib/components/Logo.svelte';
</script>
<svelte:head>
<title>Privacy Policy - Plezy</title>
<meta name="description" content="Learn how Plezy handles your data when connecting to Plex and Jellyfin. Our privacy policy covers authentication, crash diagnostics, local network information, and data storage practices." />
<link rel="canonical" href="https://plezy.app/privacy" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Plezy" />
<meta property="og:title" content="Privacy Policy - Plezy" />
<meta property="og:description" content="Learn how Plezy handles your data. Our privacy policy covers authentication, crash diagnostics, local network information, and data storage practices." />
<meta property="og:url" content="https://plezy.app/privacy" />
<meta property="og:image" content="https://plezy.app/og/plezy-social.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Privacy Policy - Plezy" />
<meta name="twitter:description" content="Learn how Plezy handles your data. Our privacy policy covers authentication, crash diagnostics, local network information, and data storage practices." />
<meta name="twitter:image" content="https://plezy.app/og/plezy-social.png" />
</svelte:head>
<article class="privacy-article">
<a href="/" class="back-link">
<span class="back-logo"><Logo /></span>
<span>Back to Plezy</span>
</a>
<h1 class="privacy-heading">Privacy Policy</h1>
<p class="last-updated">Last Updated: May 2, 2026</p>
<div class="prose">
<h2>Introduction</h2>
<p>Plezy ("we", "our", or "the app") is a third-party client for Plex and Jellyfin that allows you to access and stream content from your media server. This privacy policy explains how we handle your information when you use our app.</p>
<h2>Information We Collect</h2>
<h3>Authentication Information</h3>
<p>When you sign in to Plezy, we collect and process the following information depending on which service you connect:</p>
<ul>
<li>Plex: authentication tokens, account username, and server connection information</li>
<li>Jellyfin: server URL, access token, username, and user ID</li>
</ul>
<h3>Crash and Diagnostic Data</h3>
<p>To help us identify and fix bugs, the app automatically collects:</p>
<ul>
<li>Crash reports and error stack traces</li>
<li>Application Not Responding (ANR) events</li>
<li>Device model, OS version, and app version</li>
<li>Diagnostic breadcrumbs (e.g. playback events leading up to a crash)</li>
</ul>
<p>This data is sent to our self-hosted error tracking server (bugs.plezy.app). Sensitive information such as authentication tokens and server URLs is automatically stripped before transmission.</p>
<h3>Local Network Information</h3>
<p>To connect to your media server on your local network, we may access:</p>
<ul>
<li>Local network device discovery information</li>
<li>IP addresses of media servers on your network</li>
</ul>
<h3>Usage Information</h3>
<p>The app stores locally on your device:</p>
<ul>
<li>Your authentication session</li>
<li>App preferences and settings</li>
<li>Recently accessed content history</li>
</ul>
<h2>How We Use Your Information</h2>
<p>We use the collected information solely to:</p>
<ul>
<li>Authenticate you with your Plex or Jellyfin account</li>
<li>Connect to and communicate with your media server</li>
<li>Display your media library and stream content</li>
<li>Maintain your session and app preferences</li>
</ul>
<h2>Data Storage and Security</h2>
<ul>
<li>Authentication tokens are stored securely on your device using platform-specific secure storage mechanisms</li>
<li>App preferences are stored locally on your device</li>
<li>Crash and diagnostic data is sent to our self-hosted error tracking server (bugs.plezy.app) &mdash; no third-party services receive this data</li>
<li>All other communication is directly between your device and your media server, or with Plex's authentication services when signing in with Plex</li>
</ul>
<h2>Third-Party Services</h2>
<h3>Plex</h3>
<p>Plezy uses Plex's authentication and media services. When you use this app:</p>
<ul>
<li>You authenticate directly with Plex's servers</li>
<li>Your media streaming occurs between your device and your Plex Media Server</li>
<li>Plex's own privacy policy applies to their services: <a href="https://www.plex.tv/about/privacy-legal/" target="_blank" rel="noopener noreferrer">plex.tv/about/privacy-legal</a></li>
</ul>
<p>We do not control and are not responsible for Plex's data practices.</p>
<h3>Jellyfin</h3>
<p>Plezy can connect to your self-hosted Jellyfin server. When you use this app with Jellyfin:</p>
<ul>
<li>You authenticate directly with your Jellyfin server using your username and password, or via Quick Connect</li>
<li>Your media streaming occurs between your device and your Jellyfin server</li>
<li>No third party (including Plezy) is involved in Jellyfin authentication or playback</li>
</ul>
<p>Because Jellyfin is self-hosted, your Jellyfin server's privacy practices are determined by whoever operates it. More information about the Jellyfin project is available at <a href="https://jellyfin.org" target="_blank" rel="noopener noreferrer">jellyfin.org</a>.</p>
<h3>Litterbox (Discord Rich Presence)</h3>
<p>When Discord Rich Presence is enabled, media artwork may be temporarily uploaded to:</p>
<ul>
<li>Litterbox (litterbox.catbox.moe) &mdash; a temporary file hosting service</li>
</ul>
<p>These uploads are necessary because Discord requires publicly accessible image URLs for Rich Presence artwork. Uploaded images automatically expire after 1 hour. This feature is opt-in and disabled by default.</p>
<h2>Data Sharing</h2>
<p>We do not:</p>
<ul>
<li>Sell your personal information</li>
<li>Share your information with third parties for marketing purposes</li>
<li>Transmit your data to our own servers (except crash/diagnostic reports as described above)</li>
<li>Track your viewing habits outside of local app functionality</li>
</ul>
<p>Your data is only shared with:</p>
<ul>
<li>Plex services for authentication and media server communication, when signing in with Plex (as required for the app to function)</li>
<li>Your self-hosted Jellyfin server, when signing in with Jellyfin (as required for the app to function)</li>
</ul>
<h2>Your Rights and Choices</h2>
<p>You can:</p>
<ul>
<li>Sign out at any time to remove your authentication session from the device</li>
<li>Uninstall the app to remove all locally stored data</li>
<li>Manage your Plex account directly through Plex's services, or your Jellyfin account through your Jellyfin server</li>
</ul>
<h2>Children's Privacy</h2>
<p>Plezy does not knowingly collect personal information from children under 13. The app relies on authentication with Plex or Jellyfin servers, and users must comply with the terms of service and age requirements of whichever service they connect to.</p>
<h2>Changes to This Privacy Policy</h2>
<p>We may update this privacy policy from time to time. We will notify users of any material changes by updating the "Last Updated" date at the top of this policy.</p>
<h2>Data Retention</h2>
<ul>
<li>Authentication tokens are retained on your device until you sign out or uninstall the app</li>
<li>Local preferences and settings are retained until you uninstall the app</li>
<li>Crash and diagnostic reports are retained on our self-hosted server and automatically pruned based on relevance</li>
</ul>
<h2>International Data Transfers</h2>
<p>As we do not operate backend servers, there are no international data transfers from our side. Any data transfers occur directly between your device and either Plex's services (subject to Plex's privacy policy) or your Jellyfin server (governed by whoever operates it).</p>
<h2>Legal Compliance</h2>
<p>This app complies with:</p>
<ul>
<li>General Data Protection Regulation (GDPR)</li>
<li>California Consumer Privacy Act (CCPA)</li>
<li>Children's Online Privacy Protection Act (COPPA)</li>
<li>Other applicable privacy laws</li>
</ul>
<h2>Your Consent</h2>
<p>By using Plezy, you consent to this privacy policy and the processing of your information as described herein.</p>
</div>
</article>
<style>
.privacy-article {
max-width: 64rem;
margin-inline: auto;
padding: 4rem 1.5rem;
}
.back-link {
display: inline-flex;
align-items: center;
gap: 0.625rem;
margin-bottom: 3rem;
color: var(--color-text-muted);
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
transition: color 150ms ease;
}
.back-link:hover {
color: var(--color-text);
}
.back-logo :global(svg) {
width: 1.5rem;
height: 1.5rem;
}
.privacy-heading {
margin-bottom: 0.5rem;
font-size: 1.875rem;
font-weight: 700;
line-height: 2.25rem;
}
.last-updated {
margin-bottom: 3rem;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.25rem;
}
.prose {
color: var(--color-text-muted);
line-height: 1.75;
max-width: none;
}
.prose h2 {
color: var(--color-text);
font-size: 1.25rem;
font-weight: 600;
margin-top: 2.5rem;
margin-bottom: 0.75rem;
}
.prose h3 {
color: var(--color-text);
font-size: 1rem;
font-weight: 600;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
}
.prose p {
margin-bottom: 1rem;
}
.prose ul {
list-style: disc;
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.prose li {
margin-bottom: 0.25rem;
}
.prose a {
color: var(--color-accent);
text-decoration: underline;
text-underline-offset: 2px;
}
.prose a:hover {
opacity: 0.8;
}
@media (min-width: 640px) {
.privacy-article {
padding-block: 6rem;
}
.privacy-heading {
font-size: 2.25rem;
line-height: 2.5rem;
}
}
</style>
+142
View File
@@ -0,0 +1,142 @@
<script lang="ts">
import { browser } from "$app/environment";
import Logo from "$lib/components/Logo.svelte";
import AppleIcon from "~icons/simple-icons/apple";
import GooglePlayIcon from "~icons/simple-icons/googleplay";
type Platform = "ios" | "android" | "unknown";
let platform: Platform = $state("unknown");
if (browser) {
const ua = navigator.userAgent.toLowerCase();
if (/iphone|ipad|ipod/.test(ua)) {
platform = "ios";
} else if (/android/.test(ua)) {
platform = "android";
}
}
</script>
<svelte:head>
<title>Open in Plezy</title>
<meta name="description" content="Open this QR code with the Plezy app." />
<meta name="robots" content="noindex, nofollow" />
<link rel="canonical" href="https://plezy.app/scan" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Plezy" />
<meta property="og:title" content="Open in Plezy" />
<meta property="og:description" content="Open this QR code with the Plezy app." />
<meta property="og:url" content="https://plezy.app/scan" />
<meta property="og:image" content="https://plezy.app/og/plezy-social.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Open in Plezy" />
<meta name="twitter:description" content="Open this QR code with the Plezy app." />
<meta name="twitter:image" content="https://plezy.app/og/plezy-social.png" />
</svelte:head>
<div class="scan-page">
<div class="scan-card">
<span class="scan-logo"><Logo /></span>
<h1 class="scan-heading">Scan in Plezy</h1>
<p class="scan-description">To use this feature, scan this QR code with the Plezy app.</p>
<div class="store-buttons">
{#if platform !== "android"}
<a
href="https://apps.apple.com/us/app/id6754315964"
target="_blank"
rel="noopener noreferrer"
class="store-button"
>
<AppleIcon />
App Store
</a>
{/if}
{#if platform !== "ios"}
<a
href="https://play.google.com/store/apps/details?id=com.edde746.plezy"
target="_blank"
rel="noopener noreferrer"
class="store-button"
>
<GooglePlayIcon />
Google Play
</a>
{/if}
</div>
</div>
</div>
<style>
.scan-page {
display: flex;
min-height: 100vh;
align-items: center;
justify-content: center;
padding: 4rem 1.5rem;
}
.scan-card {
display: flex;
max-width: 24rem;
flex-direction: column;
align-items: center;
text-align: center;
}
.scan-logo {
margin-bottom: 1.5rem;
}
.scan-logo :global(svg) {
width: 4rem;
height: 4rem;
}
.scan-heading {
margin-bottom: 0.5rem;
font-size: 1.5rem;
font-weight: 700;
line-height: 2rem;
}
.scan-description {
margin-bottom: 2rem;
color: var(--color-text-muted);
}
.store-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.75rem;
}
.store-button {
display: inline-flex;
align-items: center;
gap: 0.625rem;
padding: 0.75rem 1.25rem;
border-radius: 9999px;
background: #fff;
color: #111827;
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25rem;
transition: background-color 150ms ease;
}
.store-button:hover {
background: #f3f4f6;
}
.store-button :global(svg) {
width: 1.25rem;
height: 1.25rem;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

+5
View File
@@ -0,0 +1,5 @@
# allow crawling everything by default
User-agent: *
Disallow:
Sitemap: https://plezy.app/sitemap.xml
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://plezy.app/</loc>
<priority>1.0</priority>
</url>
<url>
<loc>https://plezy.app/privacy</loc>
<priority>0.3</priority>
</url>
</urlset>
+13
View File
@@ -0,0 +1,13 @@
import adapter from "@sveltejs/adapter-static";
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter(),
},
};
export default config;
+20
View File
@@ -0,0 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}
+6
View File
@@ -0,0 +1,6 @@
import { enhancedImages } from "@sveltejs/enhanced-img";
import { sveltekit } from "@sveltejs/kit/vite";
import Icons from "unplugin-icons/vite";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [enhancedImages(), sveltekit(), Icons({ compiler: "svelte" })], preview: { allowedHosts: ["*"] } });