+16
.tangled/workflows/deploy.yml
+16
.tangled/workflows/deploy.yml
···
1
+
engine: nixery
2
+
when:
3
+
- event: ["push", "pull_request"]
4
+
branch: ["main"]
5
+
6
+
dependencies:
7
+
nixpkgs:
8
+
- bun
9
+
steps:
10
+
- name: build site
11
+
command: |
12
+
bun i && bun run build
13
+
14
+
- name: deployw
15
+
command: |
16
+
bunx wrangler pages deploy --branch main --project-name plcbundle-watch ./dist/
+3
-45
README.md
+3
-45
README.md
···
1
-
# Svelte + TS + Vite
2
-
3
-
This template should help get you started developing with Svelte and TypeScript in Vite.
4
-
5
-
## Recommended IDE Setup
6
-
7
-
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
8
-
9
-
## Need an official Svelte framework?
10
-
11
-
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
12
-
13
-
## Technical considerations
14
-
15
-
**Why use this over SvelteKit?**
16
-
17
-
- It brings its own routing solution which might not be preferable for some users.
18
-
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
19
-
20
-
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
21
-
22
-
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
23
-
24
-
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
25
-
26
-
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
27
-
28
-
**Why include `.vscode/extensions.json`?**
29
-
30
-
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
31
-
32
-
**Why enable `allowJs` in the TS template?**
33
-
34
-
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
35
-
36
-
**Why is HMR not preserving my local component state?**
37
-
38
-
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
1
+
# plcbundle-watch
39
2
40
-
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
3
+
More about plcbundle: https://tangled.org/atscan.net/plcbundle/
41
4
42
-
```ts
43
-
// store.ts
44
-
// An extremely simple external store
45
-
import { writable } from 'svelte/store'
46
-
export default writable(0)
47
-
```
5
+
https://plcbundle-watch.pages.dev
+389
bun.lock
+389
bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"workspaces": {
4
+
"": {
5
+
"name": "plcbundle-watch",
6
+
"dependencies": {
7
+
"@tailwindcss/vite": "^4.1.16",
8
+
"date-fns": "^4.1.0",
9
+
"filesize": "^11.0.13",
10
+
"lodash": "^4.17.21",
11
+
"numeral": "^2.0.6",
12
+
"tailwindcss": "^4.1.16",
13
+
},
14
+
"devDependencies": {
15
+
"@skeletonlabs/skeleton": "^4.2.2",
16
+
"@skeletonlabs/skeleton-svelte": "^4.2.2",
17
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
18
+
"@tsconfig/svelte": "^5.0.5",
19
+
"@types/lodash": "^4.17.20",
20
+
"@types/node": "^24.6.0",
21
+
"svelte": "^5.39.6",
22
+
"svelte-check": "^4.3.2",
23
+
"typescript": "~5.9.3",
24
+
"vite": "^7.1.7",
25
+
},
26
+
},
27
+
},
28
+
"packages": {
29
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
30
+
31
+
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
32
+
33
+
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
34
+
35
+
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
36
+
37
+
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
38
+
39
+
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
40
+
41
+
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
42
+
43
+
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
44
+
45
+
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
46
+
47
+
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
48
+
49
+
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
50
+
51
+
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
52
+
53
+
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
54
+
55
+
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
56
+
57
+
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
58
+
59
+
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
60
+
61
+
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
62
+
63
+
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
64
+
65
+
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
66
+
67
+
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
68
+
69
+
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
70
+
71
+
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
72
+
73
+
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
74
+
75
+
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
76
+
77
+
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
78
+
79
+
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
80
+
81
+
"@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="],
82
+
83
+
"@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "1.7.3", "@floating-ui/utils": "0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="],
84
+
85
+
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
86
+
87
+
"@internationalized/date": ["@internationalized/date@3.10.0", "", { "dependencies": { "@swc/helpers": "0.5.17" } }, "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw=="],
88
+
89
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
90
+
91
+
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
92
+
93
+
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
94
+
95
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
96
+
97
+
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
98
+
99
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
100
+
101
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.5", "", { "os": "android", "cpu": "arm64" }, "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA=="],
102
+
103
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA=="],
104
+
105
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA=="],
106
+
107
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA=="],
108
+
109
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ=="],
110
+
111
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ=="],
112
+
113
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ=="],
114
+
115
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg=="],
116
+
117
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q=="],
118
+
119
+
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA=="],
120
+
121
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw=="],
122
+
123
+
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw=="],
124
+
125
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg=="],
126
+
127
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ=="],
128
+
129
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q=="],
130
+
131
+
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg=="],
132
+
133
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.5", "", { "os": "none", "cpu": "arm64" }, "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw=="],
134
+
135
+
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w=="],
136
+
137
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg=="],
138
+
139
+
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ=="],
140
+
141
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg=="],
142
+
143
+
"@skeletonlabs/skeleton": ["@skeletonlabs/skeleton@4.2.2", "", { "peerDependencies": { "tailwindcss": "4.1.16" } }, "sha512-1sqP70RE0yfpKCEFU5mFNm7jH1Of3+C6J2SZWXiwr+wJvyCTyuJGE7yalFGv0ePXZWnFyF7t9u8NCoWc2aU+9Q=="],
144
+
145
+
"@skeletonlabs/skeleton-common": ["@skeletonlabs/skeleton-common@4.2.2", "", {}, "sha512-zd/wHR4UJgxvkw4JIBHxnRC7xZw1WH1zAEjVKcx271dwpfLIcysN1cBH0h2NypnhwNrAqrlJlLnDoqyZvUwCKw=="],
146
+
147
+
"@skeletonlabs/skeleton-svelte": ["@skeletonlabs/skeleton-svelte@4.2.2", "", { "dependencies": { "@internationalized/date": "3.10.0", "@skeletonlabs/skeleton-common": "4.2.2", "@zag-js/accordion": "1.26.4", "@zag-js/avatar": "1.26.4", "@zag-js/collapsible": "1.26.4", "@zag-js/collection": "1.26.4", "@zag-js/combobox": "1.26.4", "@zag-js/date-picker": "1.26.4", "@zag-js/dialog": "1.26.4", "@zag-js/file-upload": "1.26.4", "@zag-js/listbox": "1.26.4", "@zag-js/pagination": "1.26.4", "@zag-js/popover": "1.26.4", "@zag-js/progress": "1.26.4", "@zag-js/radio-group": "1.26.4", "@zag-js/rating-group": "1.26.4", "@zag-js/slider": "1.26.4", "@zag-js/svelte": "1.26.4", "@zag-js/switch": "1.26.4", "@zag-js/tabs": "1.26.4", "@zag-js/tags-input": "1.26.4", "@zag-js/toast": "1.26.4", "@zag-js/toggle-group": "1.26.4", "@zag-js/tooltip": "1.26.4", "@zag-js/tree-view": "1.26.4" }, "peerDependencies": { "svelte": "5.43.2" } }, "sha512-9lsV+MzUK6B5ZBkoek6QSm/JCrWq1VYtnVWrQNqsBEmKwPP+9E7x13YydOIMeeKKUWfrXXg5QTcdqQ4p5Dc2SA=="],
148
+
149
+
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.6", "", { "peerDependencies": { "acorn": "8.15.0" } }, "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ=="],
150
+
151
+
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "5.0.1", "debug": "4.4.3", "deepmerge": "4.3.1", "magic-string": "0.30.21", "vitefu": "1.1.1" }, "peerDependencies": { "svelte": "5.43.2", "vite": "7.1.12" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="],
152
+
153
+
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "4.4.3" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "6.2.1", "svelte": "5.43.2", "vite": "7.1.12" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
154
+
155
+
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
156
+
157
+
"@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "enhanced-resolve": "5.18.3", "jiti": "2.6.1", "lightningcss": "1.30.2", "magic-string": "0.30.21", "source-map-js": "1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="],
158
+
159
+
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="],
160
+
161
+
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="],
162
+
163
+
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="],
164
+
165
+
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="],
166
+
167
+
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="],
168
+
169
+
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="],
170
+
171
+
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="],
172
+
173
+
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="],
174
+
175
+
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="],
176
+
177
+
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="],
178
+
179
+
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="],
180
+
181
+
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="],
182
+
183
+
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="],
184
+
185
+
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.16", "", { "dependencies": { "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "tailwindcss": "4.1.16" }, "peerDependencies": { "vite": "7.1.12" } }, "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg=="],
186
+
187
+
"@tsconfig/svelte": ["@tsconfig/svelte@5.0.5", "", {}, "sha512-48fAnUjKye38FvMiNOj0J9I/4XlQQiZlpe9xaNPfe8vy2Y1hFBt8g1yqf2EGjVvHavo4jf2lC+TQyENCr4BJBQ=="],
188
+
189
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
190
+
191
+
"@types/lodash": ["@types/lodash@4.17.20", "", {}, "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="],
192
+
193
+
"@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="],
194
+
195
+
"@zag-js/accordion": ["@zag-js/accordion@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-gjKu1i3Z3fGzrGazMDHskbgdqj/DtHw9ZOW8L9X/G37CNkIe4EQyy1GbzyM91gYncSFNShquBawxuaGK3VvSdw=="],
196
+
197
+
"@zag-js/anatomy": ["@zag-js/anatomy@1.26.4", "", {}, "sha512-O7kedQsC/aQ+gkNo6y8ZcB0VFmQNihOMzXeglEWJdashGn5CO0xwYkLioFwfF5+u+QQtieVjof8VAc8QT41QfQ=="],
198
+
199
+
"@zag-js/aria-hidden": ["@zag-js/aria-hidden@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4" } }, "sha512-B2nPb45uc7DpPTrR415MPEVyIgGKf76bEt9ongS0bdbfUwJ8vnaIYeWK5wNh6jkbwHqca0e4vIwGzzE2S7cF6g=="],
200
+
201
+
"@zag-js/auto-resize": ["@zag-js/auto-resize@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4" } }, "sha512-zDkcfi18XWCOEWUTcONZWWZgsqEkm2nzjGFQ5uWf4UeF97ofsZ5pTc60Vzk7raB787vh4j3YxCPjVaeq3iedvg=="],
202
+
203
+
"@zag-js/avatar": ["@zag-js/avatar@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-ZTv5uJGdLkQDm5UB8vz8NLPtFW9DbOES36vIWuA2+/KuMnwISIX3JeW3fEhn4cFF8e/o7B9/NR7QXTFjjKwCoA=="],
204
+
205
+
"@zag-js/collapsible": ["@zag-js/collapsible@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-Y/SnLO3Tw4z/pWgvrO3j4eOPGN5YQxT173kYqE5c721dfK8TFbBsSH5oqjt4JE6VNCPnY9vFNc3zuXuvTLqLkg=="],
206
+
207
+
"@zag-js/collection": ["@zag-js/collection@1.26.4", "", { "dependencies": { "@zag-js/utils": "1.26.4" } }, "sha512-cpDg0TnK49xD/qnhR6pzbYZ73b1+i22eE7xlVX5nwIZdaS3pRerx3YvD7epgYYjbcm/zeKIiFQcFj+CsWjtiLg=="],
208
+
209
+
"@zag-js/combobox": ["@zag-js/combobox@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/aria-hidden": "1.26.4", "@zag-js/collection": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dismissable": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/popper": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-4Yq8nULxT1qEq+AYixZyaryQZrLjM1hL1C/QpPtUDuX0pN7jXKZTA18M3i0TtVRF+auQE3zI+1l5YTWrFQGPqw=="],
210
+
211
+
"@zag-js/core": ["@zag-js/core@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-6pqLD/RlPLV0U+Zvw1JLp1aRMfujt5c4+K5FqDNEsGDlDkP8b/n89TADuhgloFeUlfYgHzXCHTO7UHDrNKY5dQ=="],
212
+
213
+
"@zag-js/date-picker": ["@zag-js/date-picker@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/date-utils": "1.26.4", "@zag-js/dismissable": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/live-region": "1.26.4", "@zag-js/popper": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" }, "peerDependencies": { "@internationalized/date": "3.10.0" } }, "sha512-qyRqBxuF/sqSuGiio13958i+AoEvTfZDbzpZYj+2d/O7ubyTLD3TJMeTHzN97lmOa9mbQSuaL+aOOW3G7d+fWQ=="],
214
+
215
+
"@zag-js/date-utils": ["@zag-js/date-utils@1.26.4", "", { "peerDependencies": { "@internationalized/date": "3.10.0" } }, "sha512-PdOfWlzGRcQK7kefGVBOXJzmQqQXI+tHUutLWqWCa/WIq81wLXO0U4UDRPQgzL4f9JQ2UTVIdQsa9wlx4i5EXQ=="],
216
+
217
+
"@zag-js/dialog": ["@zag-js/dialog@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/aria-hidden": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dismissable": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/focus-trap": "1.26.4", "@zag-js/remove-scroll": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-glXvVGgh1ZnMtFlG0s3cub7d+C4jCkzsniuD0sNU1pWrziJFqSFAeqGAl+uTpMTs0C/jo8DpZzcGEsPmgaIhjg=="],
218
+
219
+
"@zag-js/dismissable": ["@zag-js/dismissable@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4", "@zag-js/interact-outside": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-aY1W3YRh7bP3izaW1pV2V6quZHCTUrEp0Cr/345p6j8PJefsesI75+ln6xHO+wMXJFiNPDAbWImI+4Zv2qrvLQ=="],
220
+
221
+
"@zag-js/dom-query": ["@zag-js/dom-query@1.26.4", "", { "dependencies": { "@zag-js/types": "1.26.4" } }, "sha512-Yxovh/S/9V/SLZvDwXxLXjsjRjLDHFHSgwsZh7dk7WFqDbnrQPY96rK8+Khd4EI2dMLdhmZM3AEU2IuKWKe2yQ=="],
222
+
223
+
"@zag-js/file-upload": ["@zag-js/file-upload@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/file-utils": "1.26.4", "@zag-js/i18n-utils": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-j6BqWFQ1Tgk8nFcZyYLhZ/PE0r0lbEMNsZ4JhMMlk1gI4axBLNttj51LcFmJGOlGz2q0Kh1ULun6MAO6W54zmQ=="],
224
+
225
+
"@zag-js/file-utils": ["@zag-js/file-utils@1.26.4", "", { "dependencies": { "@zag-js/i18n-utils": "1.26.4" } }, "sha512-C+5+y0exftrc3a2+HA+lxf8CSBBlF77tyuHr2noGB6TXWeUHjgDkUSn4j556d8lmbdWi0YSMqESI3eDdeFwBAw=="],
226
+
227
+
"@zag-js/focus-trap": ["@zag-js/focus-trap@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4" } }, "sha512-/75t0MroZdtyXntQ4PclmD6fQD/xvV5tiNJPc7lY88Xk0TpxaWtJZ/beS98iR47jm1i7BbhlpbL0EyBTi6RJ7w=="],
228
+
229
+
"@zag-js/focus-visible": ["@zag-js/focus-visible@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4" } }, "sha512-wKpnO/g99y9KYXZlszrHVxdI0ZEAEO/RL+5AcWqBiCuQhagm+rB9z4VeNK6K+BcOs+5PMJP2DY0UnNgmC2WbdQ=="],
230
+
231
+
"@zag-js/i18n-utils": ["@zag-js/i18n-utils@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4" } }, "sha512-3ckQ4MMlZSu8TV6VMukpdZF/IjhiOwXEE9a4WqAMKCS16bqkDb574kKXGr2ZpXCVoWXYR+8OxbxeZfWZhUGKiA=="],
232
+
233
+
"@zag-js/interact-outside": ["@zag-js/interact-outside@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-2EMqsO2/7zzIytADlfIRsHX/INkSoxnuVGOecjGY2ZUfOlOnbNngIkRUqLNDH9X814Xx5KEHeiGLvXe/xe9hOA=="],
234
+
235
+
"@zag-js/listbox": ["@zag-js/listbox@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/collection": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/focus-visible": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-LWd9U9IJuI0NPPWvxbpOXxpTRSE7fGhzSEp8LFHWpa5gDKKDdEI/jSc8MsuaNGAYQCIqINKWWjakyWzioSrzOA=="],
236
+
237
+
"@zag-js/live-region": ["@zag-js/live-region@1.26.4", "", {}, "sha512-4coDig5/SU9wkMrlDGVMYD8I314TTMTnJcvudQGZ66VvGAUn5UCRg2/vCvMn+ia7ZOB0eEPc8Jl5Gf4whAiXgg=="],
238
+
239
+
"@zag-js/pagination": ["@zag-js/pagination@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-igDpqg3YC9RRz0+aW+sS01WGBhOxCYRH5LtB+RoQoX2qjg18HwCD/gxusogrtX+2yJUj6oXFtUjId+ftetkrxA=="],
240
+
241
+
"@zag-js/popover": ["@zag-js/popover@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/aria-hidden": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dismissable": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/focus-trap": "1.26.4", "@zag-js/popper": "1.26.4", "@zag-js/remove-scroll": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-mKDtqTPEJEKVDF5MOWVRsRTswv+DxoYoOhZg5nSiLiRBrG58h9d/28UKh92lP8wtYkpelyfD4AQPCQIiLcG2Fw=="],
242
+
243
+
"@zag-js/popper": ["@zag-js/popper@1.26.4", "", { "dependencies": { "@floating-ui/dom": "1.7.4", "@zag-js/dom-query": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-CnJb30EVR+xXeO3dRhiPkUghi2B1Z0OFMRPAi9NMBnAFD++jOzsGZe71CvthQbYqP9hU85uzBzVh6qMXXpTYhg=="],
244
+
245
+
"@zag-js/progress": ["@zag-js/progress@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-w4uGApXLr0ZBo1biK9payfLWFwKhxy0iHpE2fP3uEjLctdV1jg7z1zAJ3/5qwEv5lMGKLhX/cXHaIYUL0DCf4w=="],
246
+
247
+
"@zag-js/radio-group": ["@zag-js/radio-group@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/focus-visible": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-yTYtL7dBtdU9COQyWoafNo082/1EoJvnaaO+jE7sss6t5YSRkT0PUFZhwZObsJB1hivvojXdhw65S1P/iegB7A=="],
248
+
249
+
"@zag-js/rating-group": ["@zag-js/rating-group@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-ZUh7cok2YhIp9lHxScD8LRuGpTlT38o9v9h5l3/Tm2ptb3zq8ils2L/w7yT87DKXnO7XLsdBpoXVS3Jd3JDxtw=="],
250
+
251
+
"@zag-js/remove-scroll": ["@zag-js/remove-scroll@1.26.4", "", { "dependencies": { "@zag-js/dom-query": "1.26.4" } }, "sha512-I1vHZBo2EHKLs7pQ57B/w1U+GhdpBl5CYP6t3XdamPfZURDnqjJ3WqyIlTX2w0gGgQH3Ty61lip/4cvaVU0G/A=="],
252
+
253
+
"@zag-js/slider": ["@zag-js/slider@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-8TJF6yWyAP2919u+0ldqqDnitqcmEHzmk/NioRgNtycKmsOqRw6wuf9zeiBagCQmgn/8i02dmjrYtkIAMNFpfA=="],
254
+
255
+
"@zag-js/svelte": ["@zag-js/svelte@1.26.4", "", { "dependencies": { "@zag-js/core": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" }, "peerDependencies": { "svelte": "5.43.2" } }, "sha512-V4DzemgLvnyNQqdtVfrzJ3mxA84Rakg5i+4XXUzgPA8sL1gGclNISFpOAaiyBIh0KjbKNU38t/PPwlmR+5xV+Q=="],
256
+
257
+
"@zag-js/switch": ["@zag-js/switch@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/focus-visible": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-qka+TZBwwNTuDkcCT4I/RqeG300bRDqg3eOjPIwm5fX9zy4gFfQ6dNPkycAyk/6yFwmY6ao40GYkCQlaMnD7Pw=="],
258
+
259
+
"@zag-js/tabs": ["@zag-js/tabs@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-rLFTW2R3pNlauscsSJZIlocFWU3TsQ8Bhfjc+TJQHcUBsbOPtYmuYCMuwxJBrpuKjyIt8U5DEGuzDumsVO0t5g=="],
260
+
261
+
"@zag-js/tags-input": ["@zag-js/tags-input@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/auto-resize": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/interact-outside": "1.26.4", "@zag-js/live-region": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-9SN9bsduoSjkXdzVxU/f3VYDKufa1d5IGQSsg2fa+f2UXeJ8NDOo/XVIZjAN6VO/vkZf3JuPr4H/yrsb4BUETw=="],
262
+
263
+
"@zag-js/toast": ["@zag-js/toast@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dismissable": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-Bgp3T+cXDbEollDc1woqmv6HDUydGx4MeAJcK4wh8MNwNnxb/42mTBvmffn7wCU07/N6AYroP+m1BVo58c1NPg=="],
264
+
265
+
"@zag-js/toggle-group": ["@zag-js/toggle-group@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-idOp4XkR5IAZAa7LNT74I7RBl78AUX9Ih0uthpUEBGndpjkViiZXzaU8X5a8FHBuNp5eUdNUVRM3P9zX+b5gdw=="],
266
+
267
+
"@zag-js/tooltip": ["@zag-js/tooltip@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/focus-visible": "1.26.4", "@zag-js/popper": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-a+9QPbDib/XmGxp4pp+5UoNXp7d45uAwBfmQ9VAY0+vrluDcu83lOWmhiFHOui85nY7BRCuW5iaT9TIFrewV7Q=="],
268
+
269
+
"@zag-js/tree-view": ["@zag-js/tree-view@1.26.4", "", { "dependencies": { "@zag-js/anatomy": "1.26.4", "@zag-js/collection": "1.26.4", "@zag-js/core": "1.26.4", "@zag-js/dom-query": "1.26.4", "@zag-js/types": "1.26.4", "@zag-js/utils": "1.26.4" } }, "sha512-mK56WITHqz3AIfKnZZm0P5zZ4dDa6FtE/v0LdWfvHfASjDQibtpgTwcdkQd3a6qMqHxWiq1lchTBhc21Xl3/MQ=="],
270
+
271
+
"@zag-js/types": ["@zag-js/types@1.26.4", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-vmuC9fYJeM8dbwk+dxfdX6cFPnJsPVgQGIUJATbbp+S46JyOuJV4T3ERYX3TxBEdsgoKjWD1i1OQWWc3Nywtjg=="],
272
+
273
+
"@zag-js/utils": ["@zag-js/utils@1.26.4", "", {}, "sha512-IwfMUrTZA31I2c/Oomqwqo0HoSZUVvwIejCpPvITiqtB+HZ4rmNHhSbRgz5+llsOiuBfnQ5SJvQCD4MKOHMA3w=="],
274
+
275
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
276
+
277
+
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
278
+
279
+
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
280
+
281
+
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "4.1.2" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
282
+
283
+
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
284
+
285
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
286
+
287
+
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
288
+
289
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
290
+
291
+
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
292
+
293
+
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
294
+
295
+
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "4.2.11", "tapable": "2.3.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
296
+
297
+
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
298
+
299
+
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
300
+
301
+
"esrap": ["esrap@2.1.2", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg=="],
302
+
303
+
"fdir": ["fdir@6.5.0", "", { "optionalDependencies": { "picomatch": "4.0.3" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
304
+
305
+
"filesize": ["filesize@11.0.13", "", {}, "sha512-mYJ/qXKvREuO0uH8LTQJ6v7GsUvVOguqxg2VTwQUkyTPXXRRWPdjuUPVqdBrJQhvci48OHlNGRnux+Slr2Rnvw=="],
306
+
307
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
308
+
309
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
310
+
311
+
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "1.0.8" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
312
+
313
+
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
314
+
315
+
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "2.1.2" }, "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=="],
316
+
317
+
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
318
+
319
+
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
320
+
321
+
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
322
+
323
+
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
324
+
325
+
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
326
+
327
+
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
328
+
329
+
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
330
+
331
+
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
332
+
333
+
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
334
+
335
+
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
336
+
337
+
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
338
+
339
+
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
340
+
341
+
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
342
+
343
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
344
+
345
+
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
346
+
347
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
348
+
349
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
350
+
351
+
"numeral": ["numeral@2.0.6", "", {}, "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA=="],
352
+
353
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
354
+
355
+
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
356
+
357
+
"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=="],
358
+
359
+
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
360
+
361
+
"rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "2.3.3" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="],
362
+
363
+
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "1.2.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
364
+
365
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
366
+
367
+
"svelte": ["svelte@5.43.2", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "@jridgewell/sourcemap-codec": "1.5.5", "@sveltejs/acorn-typescript": "1.0.6", "@types/estree": "1.0.8", "acorn": "8.15.0", "aria-query": "5.3.2", "axobject-query": "4.1.0", "clsx": "2.1.1", "esm-env": "1.2.2", "esrap": "2.1.2", "is-reference": "3.0.3", "locate-character": "3.0.0", "magic-string": "0.30.21", "zimmerframe": "1.1.4" } }, "sha512-ro1umEzX8rT5JpCmlf0PPv7ncD8MdVob9e18bhwqTKNoLjS8kDvhVpaoYVPc+qMwDAOfcwJtyY7ZFSDbOaNPgA=="],
368
+
369
+
"svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.31", "chokidar": "4.0.3", "fdir": "6.5.0", "picocolors": "1.1.1", "sade": "1.8.1" }, "peerDependencies": { "svelte": "5.43.2", "typescript": "5.9.3" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="],
370
+
371
+
"tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="],
372
+
373
+
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
374
+
375
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "6.5.0", "picomatch": "4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
376
+
377
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
378
+
379
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
380
+
381
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
382
+
383
+
"vite": ["vite@7.1.12", "", { "dependencies": { "esbuild": "0.25.12", "fdir": "6.5.0", "picomatch": "4.0.3", "postcss": "8.5.6", "rollup": "4.52.5", "tinyglobby": "0.2.15" }, "optionalDependencies": { "@types/node": "24.9.2", "fsevents": "2.3.3", "jiti": "2.6.1", "lightningcss": "1.30.2" }, "bin": { "vite": "bin/vite.js" } }, "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug=="],
384
+
385
+
"vitefu": ["vitefu@1.1.1", "", { "optionalDependencies": { "vite": "7.1.12" } }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
386
+
387
+
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
388
+
}
389
+
}
+7
-1
package.json
+7
-1
package.json
···
7
7
"dev": "vite",
8
8
"build": "vite build",
9
9
"preview": "vite preview",
10
-
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
10
+
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
11
+
"deploy": "bun run build && bunx wrangler pages deploy --branch main --project-name plcbundle-watch ./dist"
11
12
},
12
13
"devDependencies": {
13
14
"@skeletonlabs/skeleton": "^4.2.2",
···
15
16
"@sveltejs/vite-plugin-svelte": "^6.2.1",
16
17
"@tsconfig/svelte": "^5.0.5",
17
18
"@types/node": "^24.6.0",
19
+
"@types/lodash": "^4.17.20",
18
20
"svelte": "^5.39.6",
19
21
"svelte-check": "^4.3.2",
20
22
"typescript": "~5.9.3",
···
22
24
},
23
25
"dependencies": {
24
26
"@tailwindcss/vite": "^4.1.16",
27
+
"date-fns": "^4.1.0",
28
+
"filesize": "^11.0.13",
29
+
"lodash": "^4.17.21",
30
+
"numeral": "^2.0.6",
25
31
"tailwindcss": "^4.1.16"
26
32
}
27
33
}
+369
-67
src/App.svelte
+369
-67
src/App.svelte
···
1
1
<script lang="ts">
2
-
import { onMount } from 'svelte';
3
-
import svelteLogo from './assets/svelte.svg'
2
+
3
+
import { onMount } from 'svelte';
4
+
import { filesize } from 'filesize';
5
+
import { formatDistanceToNow, addSeconds, subSeconds, formatDate, formatISO9075, differenceInSeconds } from 'date-fns';
6
+
import { Progress, Switch } from '@skeletonlabs/skeleton-svelte';
7
+
import orderBy from 'lodash/orderBy';
8
+
import BundleDownloader from './BundleDownloader.svelte';
9
+
import { formatNumber, formatUptime } from './lib/utils';
10
+
import instancesData from './instances.json';
11
+
12
+
const APP_TITLE = 'plcbundle instances'
13
+
const PLC_DIRECTORY = 'plc.directory'
14
+
const ROOT = 'f743c3ae1e3f6023e89e492bce63b52a9ed03ee46a163c2f4a3b997eaf2aaf85'
15
+
const AUTO_REFRESH_INTERVAL = 10 // in seconds
16
+
const BUNDLE_OPS = 10_000
17
+
const PAST_ROOTS = [
18
+
// November 2025: https://bsky.app/profile/atproto.com/post/3m4e3mnxb7s2p
19
+
"cbab6809a136d6a621906ee11199d3b0faf85b422fe0d0d2c346ce8e9dcd7485"
20
+
]
21
+
22
+
type StatusResponse = {
23
+
ok: boolean;
24
+
bundles: {
25
+
last_bundle: number;
26
+
root_hash: string;
27
+
head_hash: string;
28
+
end_time?: string;
29
+
total_size?: number;
30
+
uncompressed_size?: number;
31
+
updated_at?: string;
32
+
};
33
+
server: {
34
+
uptime: number;
35
+
};
36
+
mempool?: {
37
+
count: number;
38
+
eta_next_bundle_seconds: number;
39
+
last_time: Date;
40
+
};
41
+
latency?: number;
42
+
}
43
+
44
+
type StatusResponseError = {
45
+
error: string;
46
+
}
4
47
5
48
type Instance = {
6
-
url: String,
7
-
cors?: Boolean,
8
-
status?: Object,
9
-
modern?: Boolean,
49
+
url: string;
50
+
cors?: boolean;
51
+
status?: StatusResponse | StatusResponseError;
52
+
modern?: boolean;
53
+
_head?: boolean;
54
+
_oldRoot?: boolean;
55
+
_conflict?: boolean;
56
+
}
57
+
58
+
type LastKnownBundle = {
59
+
number: number;
60
+
hash: string | null;
61
+
mempool: number | null;
62
+
mempoolPercent: number;
63
+
mempoolBundle: number;
64
+
lastTime?: Date;
65
+
time?: string;
66
+
etaNext?: Date | null;
67
+
totalSize?: number | null;
68
+
totalSizeUncompressed?: number | null;
10
69
}
11
70
12
-
let lastKnownBundle = $state({
71
+
let lastKnownBundle = $state<LastKnownBundle>({
13
72
number: 0,
14
73
hash: null,
74
+
mempool: null,
75
+
mempoolBundle: 0,
76
+
mempoolPercent: 0,
15
77
})
16
78
17
-
let instances = $state([
18
-
{ url: "https://plcbundle.atscan.net", modern: true },
19
-
{ url: "https://plc.j4ck.xyz", modern: false },
20
-
{ url: "https://plc.indexx.dev", modern: false },
21
-
])
79
+
let isUpdating = $state(false)
80
+
let canRefresh = $state(true)
81
+
let consensus = $state({})
82
+
let isConflict = $state(consensus)
83
+
let instancesInConflict = $state<string[]>([])
84
+
let lastUpdated = $state(new Date())
85
+
let autoRefreshEnabled = $state(true)
86
+
let instances = $state<Instance[]>(instancesData.sort(() => Math.random() - 0.5))
22
87
23
-
//instancesSorted = $derived(instances.sort())
88
+
const instanceOrderBy = [
89
+
['status.error', '_head', '_oldRoot' , 'status.bundles.last_bundle', 'status.latency'],
90
+
['desc', 'desc', 'asc', 'desc', 'asc']
91
+
]
24
92
25
-
async function getStatus(instance: Instance) {
26
-
let statusResp: object | undefined;
93
+
async function getStatus(instance: Instance): Promise<StatusResponse | StatusResponseError> {
94
+
let statusResp: StatusResponse | undefined;
27
95
let url: string = instance.url;
96
+
let lastError: string | undefined;
28
97
const start = performance.now();
29
-
if (instance.modern === false) {
30
-
url = `https://keyoxide.org/api/3/get/http?url=${encodeURIComponent(url)}&format=text&time=${Date.now()}`
31
-
}
32
-
if (instance.modern) {
33
-
try {
34
-
statusResp = await (await fetch(`${url}/status`)).json()
35
-
} catch (e) {}
98
+
try {
99
+
statusResp = await (await fetch(`${url}/status?${Number(new Date())}`)).json()
100
+
} catch (e: any) {
101
+
lastError = e.message;
36
102
}
37
103
if (!statusResp) {
38
-
const indexResp = await (await fetch(url)).text()
39
-
const [ _, from, to ] = indexResp?.match(/Range:\s+(\d{6}) - (\d{6})/)
40
-
statusResp = {
41
-
bundles: {
42
-
last_bundle: Number(to),
43
-
root_hash: indexResp?.match(/Root: ([a-f0-9]{64})/)[1],
44
-
head_hash: indexResp?.match(/Head: ([a-f0-9]{64})/)[1],
45
-
},
46
-
server: {
47
-
uptime: 1,
104
+
url = `https://keyoxide.org/api/3/get/http?url=${encodeURIComponent(url)}&format=text&time=${Date.now()}`
105
+
106
+
let indexResp: string | undefined;
107
+
try {
108
+
indexResp = await (await fetch(url)).text()
109
+
} catch(e: any) {
110
+
lastError = e.message;
111
+
}
112
+
const match = indexResp?.match(/Range:\s+(\d{6}) - (\d{6})/)
113
+
if (match) {
114
+
const [, from, to] = match
115
+
const rootMatch = indexResp?.match(/Root: ([a-f0-9]{64})/)
116
+
const headMatch = indexResp?.match(/Head: ([a-f0-9]{64})/)
117
+
118
+
statusResp = {
119
+
ok: true,
120
+
bundles: {
121
+
last_bundle: Number(to),
122
+
root_hash: rootMatch ? rootMatch[1] : '',
123
+
head_hash: headMatch ? headMatch[1] : '',
124
+
},
125
+
server: {
126
+
uptime: 1,
127
+
}
48
128
}
49
129
}
50
130
}
51
131
if (statusResp) {
52
-
statusResp.responseTime = performance.now() - start;
132
+
statusResp.ok = true
133
+
statusResp.latency = performance.now() - start;
134
+
}
135
+
//if (instance.url === 'https://plc.j4ck.xyz') { statusResp.bundles.head_hash = 'f3ad3544452b2c078cba24990486bb9c277a1155'; }
136
+
return statusResp ?? { error: lastError || 'unknown error' }
137
+
}
138
+
139
+
function recalculateHead() {
140
+
isConflict = false
141
+
instancesInConflict = []
142
+
const headHashes: any = {}
143
+
for (const instance of instances) {
144
+
if ((instance.status && 'error' in instance.status) || !instance.status) {
145
+
continue
146
+
}
147
+
const { head_hash, root_hash, last_bundle } = instance.status.bundles
148
+
instance._oldRoot = PAST_ROOTS.includes(root_hash)
149
+
instance._head = !instance._oldRoot && (last_bundle === lastKnownBundle.number)
150
+
151
+
if (instance._head && head_hash) {
152
+
if (!headHashes[head_hash]) {
153
+
headHashes[head_hash] = []
154
+
}
155
+
headHashes[head_hash].push(instance.url)
156
+
}
157
+
}
158
+
console.log(headHashes)
159
+
// second pass
160
+
const sorted: any = Object.fromEntries(
161
+
Object.entries(headHashes).sort(([, a]: any, [, b]: any) => b.length - a.length)
162
+
)
163
+
for (const instance of instances) {
164
+
if (Object.keys(sorted).length > 1 && Object.keys(sorted)[0] && !sorted[Object.keys(sorted)[0]].includes(instance.url)) {
165
+
instance._conflict = true
166
+
instancesInConflict.push(instance.url)
167
+
}
53
168
}
54
-
return statusResp
169
+
//const uniq = [...new Set(headHashes)]
170
+
isConflict = instancesInConflict.length > Math.ceil(instances.length/2)
55
171
}
56
172
57
173
async function doCheck() {
174
+
isUpdating = true
175
+
canRefresh = false
58
176
for (const i of instances) {
59
-
i.status = undefined
177
+
if (i.status && 'ok' in i.status) {
178
+
i.status.ok = false
179
+
}
60
180
}
61
181
62
182
await Promise.all(instances.map(async (instance) => {
63
183
const status = await getStatus(instance)
64
-
65
-
if (status?.bundles?.last_bundle > lastKnownBundle.number) {
66
-
lastKnownBundle.number = status?.bundles?.last_bundle
67
-
lastKnownBundle.hash = status?.bundles?.head_hash
184
+
if (!status) {
185
+
return false
68
186
}
187
+
69
188
instance.status = status
189
+
if ('ok' in status && status.ok) {
190
+
191
+
if (status?.bundles?.last_bundle && status.bundles.last_bundle >= lastKnownBundle.number && !PAST_ROOTS.includes(status.bundles.root_hash)) {
192
+
lastKnownBundle.number = status.bundles.last_bundle
193
+
lastKnownBundle.hash = status.bundles.head_hash
194
+
lastKnownBundle.time = status.bundles.end_time
195
+
196
+
if (status?.mempool?.count && (!lastKnownBundle.mempool || status.mempool.count > lastKnownBundle.mempool || status.bundles.last_bundle > lastKnownBundle.mempoolBundle)) {
197
+
lastKnownBundle.mempoolBundle = status.bundles.last_bundle
198
+
lastKnownBundle.mempool = status.mempool.count
199
+
lastKnownBundle.lastTime = status.mempool.last_time
200
+
lastKnownBundle.mempoolPercent = Math.round((lastKnownBundle.mempool/100)*100)/100
201
+
lastKnownBundle.etaNext = status.mempool.eta_next_bundle_seconds ? addSeconds(new Date(), status.mempool.eta_next_bundle_seconds) : null
202
+
lastKnownBundle.totalSize = status.bundles.total_size
203
+
lastKnownBundle.totalSizeUncompressed = status.bundles.uncompressed_size
204
+
}
205
+
}
206
+
}
207
+
208
+
lastUpdated = new Date()
209
+
210
+
recalculateHead()
70
211
}))
212
+
isUpdating = false
213
+
updateTitle()
214
+
setTimeout(() => { canRefresh = true }, 500)
215
+
}
71
216
72
-
instances = instances.sort((a, b) => a.status?.responseTime > b.status?.responseTime ? 1 : -1)
217
+
function normalizedVersion(version: string) {
218
+
const m = version.trim().match(/^([^\s]+)\.\d+\.\d+\-[0-9a-f]+(\+dirty|)$/)
219
+
if (m) {
220
+
return `${m[1]}+dirty`
221
+
}
222
+
return version
223
+
}
224
+
225
+
function updateTitle() {
226
+
const arr: string[] = []
227
+
if (lastUpdated) {
228
+
const upCount = instances.filter(i => i._head && !i._conflict)
229
+
arr.push(`${isConflict ? 'โ ๏ธ' : 'โ
'} [${upCount.length}/${instances.length}]`)
230
+
}
231
+
document.title = [...arr, APP_TITLE].join(' ')
232
+
return true
233
+
}
234
+
235
+
function bundleAge(instance: Instance) {
236
+
if (!instance.status || !('bundles' in instance.status) || !instance.status.bundles?.updated_at) {
237
+
return '-'
238
+
}
239
+
const str = instance.status?.bundles.updated_at ? instance.status?.bundles.updated_at : instance.status?.bundles.end_time || ''
240
+
const diff = differenceInSeconds(new Date(), new Date(str))
241
+
return diff > 300 ? '>5m' : diff + 's'
242
+
}
73
243
244
+
function secondsAge(seconds: number) {
245
+
return seconds > 300 ? '>5m' : seconds + 's'
74
246
}
75
247
76
-
onMount(() => {
77
-
doCheck()
248
+
let autoRefreshTimer: ReturnType<typeof setTimeout> | null = null;
249
+
250
+
onMount(() => {
251
+
doCheck().then(() => {
252
+
const scheduleRefresh = () => {
253
+
autoRefreshTimer = setTimeout(() => {
254
+
if (autoRefreshEnabled) {
255
+
doCheck()
256
+
}
257
+
scheduleRefresh()
258
+
}, AUTO_REFRESH_INTERVAL * 1000)
259
+
}
260
+
261
+
scheduleRefresh()
262
+
263
+
})
264
+
265
+
return () => {
266
+
if (autoRefreshTimer) {
267
+
clearTimeout(autoRefreshTimer)
268
+
}
269
+
}
78
270
})
79
-
80
-
<script>
81
271
</script>
82
272
83
-
<main class="w-full mt-10">
84
-
<div class="max-w-4xl mx-auto px-3">
273
+
<main class="w-full mt-10 mb-16">
274
+
<div class="max-w-5xl mx-auto px-3">
85
275
86
-
<header>
87
-
<h1 class="text-3xl">plcbundle instances</h1>
276
+
<header class="flex items-center gap-10 flex-wrap">
277
+
<div class="grow">
278
+
<h1 class="text-3xl linear-text-gradient"><a href="https://plcbundle-watch.pages.dev/" class="no-style">plcbundle instances</a></h1>
279
+
</div>
280
+
<div class="flex items-center gap-6">
281
+
<Switch class="opacity-75" checked={autoRefreshEnabled} onCheckedChange={(x) => autoRefreshEnabled = x.checked} disabled={isUpdating}>
282
+
<Switch.Control class="data-[state=checked]:preset-filled-success-500">
283
+
<Switch.Thumb />
284
+
</Switch.Control>
285
+
<Switch.Label>Auto-refresh ({AUTO_REFRESH_INTERVAL}s)</Switch.Label>
286
+
<Switch.HiddenInput />
287
+
</Switch>
288
+
<button type="button" class="btn btn-sm preset-tonal-primary" onclick={() => doCheck()} disabled={isUpdating || canRefresh === false}>Refresh</button>
289
+
</div>
88
290
</header>
89
291
90
-
<div class="flex items-center gap-2 mt-10 flex-wrap">
91
-
<div class="grow flex items-center text-lg">
92
-
<div><span class="opacity-50">Last known bundle:</span> <span class="font-semibold">{lastKnownBundle.number}</span> [<span class="font-mono text-base">{lastKnownBundle?.hash?.slice(0, 7)}</span>]</div>
292
+
<div class="gap-10 mt-6 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
293
+
<div>
294
+
<h2 class="opacity-75 text-sm">Last known bundle</h2>
295
+
<div>
296
+
<div class="flex items-center gap-5">
297
+
<div class="font-semibold text-3xl">{lastKnownBundle.number}</div>
298
+
{#if !isConflict}
299
+
<div class="mt-1 font-mono badge preset-outlined-primary-500 text-xs">{lastKnownBundle?.hash?.slice(0, 7)}</div>
300
+
{:else}
301
+
<div class="mt-1 badge preset-filled-error-500">โ ๏ธ conflict!</div>
302
+
{/if}
303
+
</div>
304
+
<div>
305
+
<span class="opacity-50">{#if lastKnownBundle?.time} {formatDistanceToNow(lastKnownBundle.time, { addSuffix: true })}{/if}</span>
306
+
</div>
307
+
<div class="mt-1">
308
+
{#if instancesInConflict.length > 0}
309
+
โ ๏ธ Fork alert on {instancesInConflict.length} instances!
310
+
{:else if !isConflict}
311
+
โ
Everything fine!
312
+
{/if}
313
+
</div>
314
+
</div>
93
315
</div>
94
-
<div class="">
95
-
<button type="button" class="btn btn-sm preset-tonal-primary" onclick={() => doCheck()}>Refresh</button>
316
+
<div>
317
+
<div>
318
+
<h2 class="opacity-75 text-sm">Next bundle</h2>
319
+
</div>
320
+
<div class="flex gap-4">
321
+
<div class="mt-4">
322
+
<Progress value={lastKnownBundle.mempoolPercent} class="items-center">
323
+
<Progress.Circle style="--size: 64px; --thickness: 10px;" class={lastKnownBundle.mempoolPercent > 95 ? 'animate-pulse' : ''}>
324
+
<Progress.CircleTrack />
325
+
<Progress.CircleRange />
326
+
</Progress.Circle>
327
+
<!--Progress.ValueText class="text-xs opacity-50" /-->
328
+
</Progress>
329
+
</div>
330
+
{#if lastKnownBundle.number > 0}
331
+
<div>
332
+
<div class="font-semibold text-2xl animate-pulse">{lastKnownBundle.number + 1}</div>
333
+
<div>{formatNumber(lastKnownBundle.mempool || 0)} / {formatNumber(BUNDLE_OPS)} <span class="opacity-50">({lastKnownBundle.mempoolPercent}%)</span></div>
334
+
{#if lastKnownBundle.etaNext}
335
+
<div class="mt-1 opacity-50">ETA: {formatDistanceToNow(lastKnownBundle.etaNext)}</div>
336
+
{/if}
337
+
</div>
338
+
{/if}
339
+
</div>
96
340
</div>
341
+
{#if lastKnownBundle.number > 0}
342
+
<div class="">
343
+
<div>
344
+
<h2 class="opacity-75 text-sm">Statistics</h2>
345
+
</div>
346
+
<div class="mt-2 grid grid-cols-1 gap-1">
347
+
<div><span class="opacity-50">Instances:</span> {instances.filter(i => i._head && !i._conflict).length} latest / {instances.length} total</div>
348
+
<div><span class="opacity-50">PLC Operations:</span> {formatNumber((lastKnownBundle.number * BUNDLE_OPS) + (lastKnownBundle.mempool || 0))}</div>
349
+
<div><span class="opacity-50">Bundles Size:</span> {#if lastKnownBundle.totalSize}{filesize(lastKnownBundle.totalSize)}{/if}</div>
350
+
<div><span class="opacity-50">Uncompressed:</span> {#if lastKnownBundle.totalSizeUncompressed}{filesize(lastKnownBundle.totalSizeUncompressed)}{/if}</div>
351
+
</div>
352
+
</div>
353
+
{/if}
97
354
</div>
98
355
99
-
<table class="table mt-4">
356
+
<table class="table mt-10">
100
357
<thead>
101
358
<tr>
102
359
<th>endpoint</th>
103
-
<th>status</th>
104
-
<th>last bundle</th>
360
+
<th>ok?</th>
361
+
<th>last</th>
362
+
<th>mempool</th>
363
+
<th>age</th>
105
364
<th>head</th>
106
365
<th>root</th>
107
366
<th>version</th>
108
-
<th>rtt</th>
367
+
<th>rsv?</th>
368
+
<th>ws?</th>
369
+
<th>uptime</th>
370
+
<th>latency</th>
109
371
</tr>
110
372
</thead>
111
-
<tbody>
112
-
{#each instances as instance}
373
+
<tbody class="[&>tr]:hover:bg-primary-500/10">
374
+
{#each orderBy(instances, ...instanceOrderBy) as instance}
113
375
<tr>
114
376
<td><a href={instance.url} target="_blank" class="font-semibold">{instance.url.replace("https://", "")}</a></td>
115
-
<td>{#if instance.status?.bundles?.last_bundle === lastKnownBundle.number}โ
{:else if instance.status}๐{/if}</td>
116
-
<td>{#if instance.status?.bundles?.last_bundle}{instance.status?.bundles?.last_bundle}{/if}</td>
117
-
<td><span class="font-mono text-xs">{#if instance.status?.bundles?.head_hash}{instance.status?.bundles?.head_hash.slice(0, 7)}{/if}</span></td>
118
-
<td><span class="font-mono text-xs">{#if instance.status?.bundles?.root_hash}{instance.status?.bundles?.root_hash.slice(0, 7)}{/if}</span></td>
119
-
<td>{#if instance.status?.server?.version}{instance.status?.server?.version}{/if}</td>
120
-
<td class="opacity-50">{#if instance.status?.responseTime}{instance.status?.responseTime}ms{/if}</td>
377
+
<td>{#if instance._head && instance.status?.ok}{#if instance._conflict}โ ๏ธ{:else}โ
{/if}{:else if instance.status?.ok && instance._oldRoot}๐ฆ{:else if instance.status?.ok}๐{:else if instance.status?.error}โ{:else}โ{/if}</td>
378
+
{#if instance.status?.error}
379
+
<td colspan="8" class="opacity-50 text-xs">Error: {instance.status?.error}</td>
380
+
{:else}
381
+
<td>{#if instance.status?.bundles?.last_bundle}<span class="{instance._conflict ? 'text-error-600' : ''}">{instance.status?.bundles?.last_bundle}</span>{/if}</td>
382
+
<td>{#if instance.status?.mempool && (instance._head || instance._oldRoot)}<span class="{instance._conflict ? 'text-error-600' : (instance._oldRoot ? 'text-warning-500 opacity-50' : '')}">{formatNumber(instance.status?.mempool.count)}</span>{:else if instance.status?.error}<span class="opacity-25 text-xs">error</span>{:else if instance.status}<span class="opacity-25 text-xs">syncing</span>{/if}</td>
383
+
<td>
384
+
{#if instance.status?.mempool}
385
+
<span class="text-xs opacity-50 {instance._conflict ? 'text-error-600' : (instance._oldRoot ? 'text-warning-500' : '')}">
386
+
{#if instance._head || instance._oldRoot}
387
+
{secondsAge(instance.status?.mempool.last_op_age_seconds || 0)}
388
+
{:else}
389
+
{bundleAge(instance)}
390
+
{/if}
391
+
</span>
392
+
{/if}
393
+
</td>
394
+
<td><span class="font-mono text-xs {instance._head ? (instance._conflict ? 'text-error-600' : 'text-success-600') : (instance._oldRoot ? 'text-warning-500 opacity-50' : 'opacity-50')}">{#if instance.status?.bundles?.head_hash}{instance.status?.bundles?.head_hash.slice(0, 7)}{/if}</span></td>
395
+
<td><span class="font-mono text-xs {instance.status ? (instance.status?.bundles?.root_hash === ROOT ? 'text-success-600' : (PAST_ROOTS.includes(instance.status?.bundles?.root_hash) ? 'text-warning-500' : 'text-error-600')) : ''}">{#if instance.status?.bundles?.root_hash}{instance.status?.bundles?.root_hash.slice(0, 7)}{/if}</span></td>
396
+
<td class="text-xs">{#if instance.status?.server?.version}<span title={instance.status?.server?.version}>{normalizedVersion(instance.status?.server?.version)}</span>{/if}</td>
397
+
<td class="text-xs">{#if instance.status?.server?.resolver_enabled}โ๏ธ{:else if instance.status}<span class="opacity-25">-</span>{/if}</td>
398
+
<td class="text-xs">{#if instance.status?.server?.websocket_enabled}โ๏ธ{:else if instance.status}<span class="opacity-25">-</span>{/if}</td>
399
+
{/if}
400
+
<td class="text-xs">{#if instance.status?.server?.uptime_seconds}{formatUptime(instance.status?.server?.uptime_seconds)}{/if}</td>
401
+
<td class="text-xs opacity-50">{#if instance.status?.latency}<a href="{instance.url}/status">{Math.round(instance.status?.latency)}ms</a>{/if}</td>
121
402
</tr>
122
403
{/each}
123
404
</tbody>
124
405
</table>
125
406
126
-
<div class="mt-12 opacity-50">
127
-
Source: <a href="https://tangled.org/atscan.net/plcbundle-watch">https://tangled.org/atscan.net/plcbundle-watch</a>
407
+
408
+
<div class="mt-12">
409
+
<div>
410
+
<span class="opacity-75">PLC Directory:</span> <a href="https://{PLC_DIRECTORY}">{PLC_DIRECTORY}</a> <span class="opacity-50">(origin)</span>
411
+
</div>
412
+
<div class="mt-2">
413
+
<span class="opacity-75">First hash (root):</span> <span class="font-mono text-xs">{ROOT.slice(0)}</span>
414
+
</div>
415
+
416
+
<div class="mt-6 opacity-50">
417
+
Last updated: {formatISO9075(lastUpdated)}
418
+
</div>
419
+
</div>
420
+
<!--hr class="hr my-10" /-->
421
+
<!--BundleDownloader instances={instances} /-->
422
+
423
+
<hr class="hr mb-6 mt-12" />
424
+
<div class="opacity-50">
425
+
<div class="mt-4 text-sm">
426
+
<a href="https://tangled.org/@tree.fail/plcbundle-watch">Source Code</a> | โค๏ธ Made with love for <a href="https://atproto.com/">#atproto</a> community by <a href="https://bsky.app/profile/tree.fail">@tree.fail</a> using <a href="https://vite.dev/">Vite</a> & <a href="https://svelte.dev/">Svelte</a>
427
+
</div>
128
428
</div>
429
+
430
+
129
431
</div>
130
432
</main>
131
433
+508
src/BundleDownloader.svelte
+508
src/BundleDownloader.svelte
···
1
+
<script lang="ts">
2
+
import { Progress } from '@skeletonlabs/skeleton-svelte';
3
+
import { tick } from 'svelte';
4
+
5
+
type Instance = {
6
+
url: string;
7
+
name?: string;
8
+
}
9
+
10
+
type InstanceStatus = {
11
+
url: string;
12
+
lastBundle: number;
13
+
}
14
+
15
+
type DownloadedBundle = {
16
+
number: number;
17
+
status: 'downloading' | 'success' | 'error' | 'cancelled';
18
+
size?: number;
19
+
error?: string;
20
+
source?: string;
21
+
}
22
+
23
+
let { instances = [] }: { instances: Instance[] } = $props();
24
+
25
+
let selectedInstance = $state('random');
26
+
let bundlesInput = $state('');
27
+
let isDownloading = $state(false);
28
+
let downloadedBundles = $state<DownloadedBundle[]>([]);
29
+
let progress = $state(0);
30
+
let totalBundles = $state(0);
31
+
let abortController: AbortController | null = null;
32
+
let isStopping = $state(false);
33
+
let instanceStatuses = $state<InstanceStatus[]>([]);
34
+
let useDirectory = $state(true);
35
+
let directoryHandle: FileSystemDirectoryHandle | null = null;
36
+
let hasFileSystemAccess = $state(false);
37
+
38
+
// Check if File System Access API is available
39
+
$effect(() => {
40
+
hasFileSystemAccess = 'showDirectoryPicker' in window;
41
+
if (!hasFileSystemAccess) {
42
+
useDirectory = false;
43
+
}
44
+
});
45
+
46
+
async function pickDirectory(): Promise<boolean> {
47
+
if (!hasFileSystemAccess) {
48
+
return false;
49
+
}
50
+
51
+
try {
52
+
directoryHandle = await (window as any).showDirectoryPicker({
53
+
mode: 'readwrite'
54
+
});
55
+
return true;
56
+
} catch (e) {
57
+
if ((e as Error).name !== 'AbortError') {
58
+
console.error('Failed to pick directory:', e);
59
+
}
60
+
return false;
61
+
}
62
+
}
63
+
64
+
async function fetchInstanceStatuses() {
65
+
const statuses: InstanceStatus[] = [];
66
+
67
+
await Promise.all(instances.map(async (instance) => {
68
+
try {
69
+
const response = await fetch(`${instance.url}/status`, {
70
+
signal: abortController?.signal
71
+
});
72
+
const data = await response.json();
73
+
statuses.push({
74
+
url: instance.url,
75
+
lastBundle: data.bundles.last_bundle
76
+
});
77
+
} catch (e) {
78
+
console.warn(`Failed to fetch status from ${instance.url}`, e);
79
+
}
80
+
}));
81
+
82
+
return statuses;
83
+
}
84
+
85
+
function getAvailableInstancesForBundle(bundleNumber: number): string[] {
86
+
return instanceStatuses
87
+
.filter(s => s.lastBundle >= bundleNumber)
88
+
.map(s => s.url);
89
+
}
90
+
91
+
function getRandomInstance(bundleNumber?: number): Instance | null {
92
+
let availableUrls: string[];
93
+
94
+
if (bundleNumber !== undefined && instanceStatuses.length > 0) {
95
+
availableUrls = getAvailableInstancesForBundle(bundleNumber);
96
+
if (availableUrls.length === 0) {
97
+
return null;
98
+
}
99
+
} else {
100
+
availableUrls = instances.map(i => i.url);
101
+
}
102
+
103
+
const randomUrl = availableUrls[Math.floor(Math.random() * availableUrls.length)];
104
+
return instances.find(i => i.url === randomUrl) || null;
105
+
}
106
+
107
+
function getInstanceUrl(bundleNumber?: number): string | null {
108
+
if (selectedInstance === 'random') {
109
+
const instance = getRandomInstance(bundleNumber);
110
+
return instance?.url || null;
111
+
}
112
+
return selectedInstance;
113
+
}
114
+
115
+
function getInstanceName(url: string): string {
116
+
const instance = instances.find(i => i.url === url);
117
+
return instance?.name || new URL(url).hostname;
118
+
}
119
+
120
+
function parseBundlesInput(input: string): number[] | 'all' {
121
+
const trimmed = input.trim();
122
+
123
+
if (!trimmed) {
124
+
return 'all';
125
+
}
126
+
127
+
if (trimmed.includes('-')) {
128
+
const [start, end] = trimmed.split('-').map(s => parseInt(s.trim()));
129
+
if (isNaN(start) || isNaN(end) || start > end) {
130
+
throw new Error('Invalid range format');
131
+
}
132
+
const bundles = [];
133
+
for (let i = start; i <= end; i++) {
134
+
bundles.push(i);
135
+
}
136
+
return bundles;
137
+
}
138
+
139
+
const num = parseInt(trimmed);
140
+
if (isNaN(num)) {
141
+
throw new Error('Invalid bundle number');
142
+
}
143
+
return [num];
144
+
}
145
+
146
+
async function getLastBundle(instanceUrl: string): Promise<number> {
147
+
const response = await fetch(`${instanceUrl}/status`, {
148
+
signal: abortController?.signal
149
+
});
150
+
const data = await response.json();
151
+
return data.bundles.last_bundle;
152
+
}
153
+
154
+
async function downloadBundle(instanceUrl: string, bundleNumber: number): Promise<Blob> {
155
+
const response = await fetch(`${instanceUrl}/data/${bundleNumber}`, {
156
+
signal: abortController?.signal
157
+
});
158
+
if (!response.ok) {
159
+
throw new Error(`HTTP ${response.status}`);
160
+
}
161
+
return await response.blob();
162
+
}
163
+
164
+
function padBundleNumber(num: number): string {
165
+
return num.toString().padStart(6, '0');
166
+
}
167
+
168
+
async function saveFileToDirectory(blob: Blob, bundleNumber: number) {
169
+
if (!directoryHandle) {
170
+
throw new Error('No directory selected');
171
+
}
172
+
173
+
const fileName = `${padBundleNumber(bundleNumber)}.jsonl.zst`;
174
+
const fileHandle = await directoryHandle.getFileHandle(fileName, { create: true });
175
+
const writable = await fileHandle.createWritable();
176
+
await writable.write(blob);
177
+
await writable.close();
178
+
}
179
+
180
+
function saveFileBrowser(blob: Blob, bundleNumber: number) {
181
+
const url = URL.createObjectURL(blob);
182
+
const link = document.createElement('a');
183
+
link.href = url;
184
+
link.download = `${padBundleNumber(bundleNumber)}.jsonl.zst`;
185
+
link.click();
186
+
URL.revokeObjectURL(url);
187
+
}
188
+
189
+
function stopDownload() {
190
+
if (abortController) {
191
+
isStopping = true;
192
+
abortController.abort();
193
+
}
194
+
}
195
+
196
+
async function handleDownload() {
197
+
if (!selectedInstance) {
198
+
alert('Please select an instance');
199
+
return;
200
+
}
201
+
202
+
// If using directory mode, pick directory first
203
+
if (useDirectory && hasFileSystemAccess) {
204
+
const picked = await pickDirectory();
205
+
if (!picked) {
206
+
return; // User cancelled
207
+
}
208
+
}
209
+
210
+
let bundleNumbers: number[];
211
+
abortController = new AbortController();
212
+
isStopping = false;
213
+
214
+
try {
215
+
if (selectedInstance === 'random') {
216
+
instanceStatuses = await fetchInstanceStatuses();
217
+
if (instanceStatuses.length === 0) {
218
+
alert('No instances available');
219
+
return;
220
+
}
221
+
}
222
+
223
+
const parsed = parseBundlesInput(bundlesInput);
224
+
225
+
if (parsed === 'all') {
226
+
let lastBundle: number;
227
+
228
+
if (selectedInstance === 'random') {
229
+
lastBundle = Math.max(...instanceStatuses.map(s => s.lastBundle));
230
+
} else {
231
+
lastBundle = await getLastBundle(selectedInstance);
232
+
}
233
+
234
+
bundleNumbers = [];
235
+
for (let i = 1; i <= lastBundle; i++) {
236
+
bundleNumbers.push(i);
237
+
}
238
+
} else {
239
+
bundleNumbers = parsed;
240
+
}
241
+
} catch (e) {
242
+
if (e instanceof Error && e.name === 'AbortError') {
243
+
return;
244
+
}
245
+
alert(e instanceof Error ? e.message : 'Invalid input');
246
+
return;
247
+
}
248
+
249
+
isDownloading = true;
250
+
downloadedBundles = [];
251
+
progress = 0;
252
+
totalBundles = bundleNumbers.length;
253
+
254
+
for (let i = 0; i < bundleNumbers.length; i++) {
255
+
if (abortController?.signal.aborted) {
256
+
break;
257
+
}
258
+
259
+
const bundleNum = bundleNumbers[i];
260
+
const instanceUrl = getInstanceUrl(bundleNum);
261
+
262
+
if (!instanceUrl) {
263
+
downloadedBundles = [...downloadedBundles, {
264
+
number: bundleNum,
265
+
status: 'error',
266
+
error: 'No instance has this bundle',
267
+
}];
268
+
progress = Math.round(((i + 1) / totalBundles) * 100);
269
+
await tick();
270
+
continue;
271
+
}
272
+
273
+
downloadedBundles = [...downloadedBundles, {
274
+
number: bundleNum,
275
+
status: 'downloading',
276
+
source: instanceUrl,
277
+
}];
278
+
279
+
await tick();
280
+
281
+
try {
282
+
const blob = await downloadBundle(instanceUrl, bundleNum);
283
+
284
+
if (abortController?.signal.aborted) {
285
+
downloadedBundles = downloadedBundles.map(b =>
286
+
b.number === bundleNum ? { ...b, status: 'cancelled' as const } : b
287
+
);
288
+
break;
289
+
}
290
+
291
+
// Save file
292
+
if (useDirectory && directoryHandle) {
293
+
await saveFileToDirectory(blob, bundleNum);
294
+
} else {
295
+
saveFileBrowser(blob, bundleNum);
296
+
}
297
+
298
+
downloadedBundles = downloadedBundles.map(b =>
299
+
b.number === bundleNum
300
+
? { ...b, status: 'success' as const, size: blob.size }
301
+
: b
302
+
);
303
+
304
+
} catch (e) {
305
+
if (e instanceof Error && e.name === 'AbortError') {
306
+
downloadedBundles = downloadedBundles.map(b =>
307
+
b.number === bundleNum ? { ...b, status: 'cancelled' as const } : b
308
+
);
309
+
break;
310
+
}
311
+
312
+
downloadedBundles = downloadedBundles.map(b =>
313
+
b.number === bundleNum
314
+
? { ...b, status: 'error' as const, error: e instanceof Error ? e.message : 'Unknown error' }
315
+
: b
316
+
);
317
+
}
318
+
319
+
progress = Math.round(((i + 1) / totalBundles) * 100);
320
+
await tick();
321
+
}
322
+
323
+
isDownloading = false;
324
+
isStopping = false;
325
+
abortController = null;
326
+
directoryHandle = null;
327
+
}
328
+
329
+
function formatBytes(bytes: number): string {
330
+
if (bytes === 0) return '0 B';
331
+
const k = 1024;
332
+
const sizes = ['B', 'KB', 'MB', 'GB'];
333
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
334
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
335
+
}
336
+
337
+
function clearResults() {
338
+
downloadedBundles = [];
339
+
progress = 0;
340
+
totalBundles = 0;
341
+
}
342
+
343
+
let successCount = $derived(downloadedBundles.filter(b => b.status === 'success').length);
344
+
let errorCount = $derived(downloadedBundles.filter(b => b.status === 'error').length);
345
+
let cancelledCount = $derived(downloadedBundles.filter(b => b.status === 'cancelled').length);
346
+
let totalSize = $derived(downloadedBundles.reduce((sum, b) => sum + (b.size || 0), 0));
347
+
</script>
348
+
349
+
<div class="bundle-downloader card space-y-4">
350
+
<h2 class="text-2xl">Bundle Downloader</h2>
351
+
352
+
<div class="space-y-3">
353
+
<label class="label">
354
+
<span>Instance</span>
355
+
<select
356
+
class="select p-3 text-sm"
357
+
bind:value={selectedInstance}
358
+
disabled={isDownloading}
359
+
>
360
+
<option value="random">๐ฒ Random (each bundle from different source)</option>
361
+
{#each instances as instance}
362
+
<option value={instance.url}>
363
+
{instance.name || instance.url}
364
+
</option>
365
+
{/each}
366
+
</select>
367
+
</label>
368
+
369
+
<label class="label">
370
+
<span>Bundles</span>
371
+
<input
372
+
class="input text-sm"
373
+
type="text"
374
+
bind:value={bundlesInput}
375
+
disabled={isDownloading}
376
+
placeholder="empty = all, 5 = single, 1-10 = range"
377
+
/>
378
+
<p class="text-xs opacity-75 mt-1">
379
+
Leave empty for all bundles, enter a number (e.g., <code>5</code>) or range (e.g., <code>1-10</code>)
380
+
</p>
381
+
</label>
382
+
383
+
{#if hasFileSystemAccess}
384
+
<label class="flex items-center space-x-2">
385
+
<input
386
+
type="checkbox"
387
+
class="checkbox"
388
+
bind:checked={useDirectory}
389
+
disabled={isDownloading}
390
+
/>
391
+
<span class="text-sm">
392
+
๐ Save to directory (recommended for multiple files)
393
+
</span>
394
+
</label>
395
+
{:else}
396
+
<div class="alert variant-ghost-warning p-2 text-xs">
397
+
<span>โ ๏ธ Directory mode not available in this browser. Files will download individually.</span>
398
+
</div>
399
+
{/if}
400
+
401
+
<div class="flex gap-2">
402
+
{#if !isDownloading}
403
+
<button
404
+
class="btn preset-tonal-primary flex-1"
405
+
onclick={handleDownload}
406
+
>
407
+
{useDirectory && hasFileSystemAccess ? '๐ Choose Directory & Download' : '๐ฅ Download'}
408
+
</button>
409
+
{#if downloadedBundles.length > 0}
410
+
<button
411
+
class="btn preset-tonal-surface"
412
+
onclick={clearResults}
413
+
>
414
+
๐๏ธ Clear
415
+
</button>
416
+
{/if}
417
+
{:else}
418
+
<button
419
+
class="btn preset-filled-error-500 flex-1"
420
+
onclick={stopDownload}
421
+
disabled={isStopping}
422
+
>
423
+
{isStopping ? 'โณ Stopping...' : 'โ Stop'}
424
+
</button>
425
+
{/if}
426
+
</div>
427
+
428
+
{#if isDownloading}
429
+
<div class="space-y-2">
430
+
<Progress value={progress} max={100} />
431
+
<p class="text-sm text-center font-semibold">
432
+
{progress}% ({successCount}/{totalBundles})
433
+
</p>
434
+
</div>
435
+
{/if}
436
+
</div>
437
+
438
+
{#if downloadedBundles.length > 0}
439
+
<div class="space-y-2">
440
+
<h3 class="text-2xl">
441
+
{isDownloading ? 'Downloading...' : 'Results'}
442
+
({successCount}/{downloadedBundles.length})
443
+
</h3>
444
+
445
+
<div class="table-container max-h-64 overflow-y-auto">
446
+
<table class="table table-compact table-hover">
447
+
<thead>
448
+
<tr>
449
+
<th>File</th>
450
+
<th>Source</th>
451
+
<th>Status</th>
452
+
<th class="text-right">Size</th>
453
+
</tr>
454
+
</thead>
455
+
<tbody>
456
+
{#each downloadedBundles as bundle (bundle.number)}
457
+
<tr>
458
+
<td class="font-mono text-xs">{padBundleNumber(bundle.number)}.jsonl.zst</td>
459
+
<td class="text-xs" title={bundle.source}>
460
+
{bundle.source ? getInstanceName(bundle.source) : '-'}
461
+
</td>
462
+
<td>
463
+
{#if bundle.status === 'downloading'}
464
+
<span class="badge variant-filled text-xs">โณ Downloading</span>
465
+
{:else if bundle.status === 'success'}
466
+
<span class="badge variant-filled-success text-xs">โ
Success</span>
467
+
{:else if bundle.status === 'cancelled'}
468
+
<span class="badge variant-filled-warning text-xs">โ ๏ธ Cancelled</span>
469
+
{:else}
470
+
<span class="badge variant-filled-error text-xs" title={bundle.error}>โ Error</span>
471
+
{/if}
472
+
</td>
473
+
<td class="text-sm text-right">{bundle.size ? formatBytes(bundle.size) : '-'}</td>
474
+
</tr>
475
+
{/each}
476
+
</tbody>
477
+
</table>
478
+
</div>
479
+
480
+
<div class="card p-3 variant-ghost-surface grid grid-cols-4 gap-2 text-sm">
481
+
<div>
482
+
<div class="font-bold text-success-500">
483
+
{successCount}
484
+
</div>
485
+
<div class="opacity-75">Success</div>
486
+
</div>
487
+
<div>
488
+
<div class="font-bold text-error-500">
489
+
{errorCount}
490
+
</div>
491
+
<div class="opacity-75">Failed</div>
492
+
</div>
493
+
<div>
494
+
<div class="font-bold text-warning-500">
495
+
{cancelledCount}
496
+
</div>
497
+
<div class="opacity-75">Cancelled</div>
498
+
</div>
499
+
<div>
500
+
<div class="font-bold">
501
+
{formatBytes(totalSize)}
502
+
</div>
503
+
<div class="opacity-75">Total</div>
504
+
</div>
505
+
</div>
506
+
</div>
507
+
{/if}
508
+
</div>
+10
-3
src/app.css
+10
-3
src/app.css
···
5
5
@import '@skeletonlabs/skeleton/themes/cerberus';
6
6
7
7
.table th {
8
-
@apply text-xs opacity-50 font-light px-3 text-left;
8
+
@apply text-xs opacity-50 font-light px-2 text-left;
9
9
}
10
10
11
11
.table td {
12
-
@apply py-1.5 px-3;
12
+
@apply py-1.5 px-2;
13
13
}
14
14
15
-
a {
15
+
a:not(.no-style) {
16
16
@apply underline hover:no-underline;
17
17
}
18
18
···
22
22
23
23
[data-theme='cerberus'] {
24
24
--text-scaling: 1.3;
25
+
}
26
+
27
+
.linear-text-gradient {
28
+
background: linear-gradient(325deg, var(--color-surface-50), var(--color-primary-500));
29
+
-webkit-background-clip: text;
30
+
background-clip: text;
31
+
color: transparent;
25
32
}
+51
src/instances.json
+51
src/instances.json
···
1
+
[
2
+
{
3
+
"url": "https://plcbundle.atscan.net",
4
+
"country": "AT",
5
+
"maintainer": "@tree.fail"
6
+
},
7
+
{
8
+
"url": "https://plcbundle2.atscan.net",
9
+
"country": "CZ",
10
+
"maintainer": "@tree.fail"
11
+
},
12
+
{
13
+
"url": "https://plcbundle3.atscan.net",
14
+
"country": "CZ",
15
+
"maintainer": "@tree.fail"
16
+
},
17
+
{
18
+
"url": "https://plc.j4ck.xyz",
19
+
"country": "UK",
20
+
"maintainer": "@j4ck.xyz"
21
+
},
22
+
{
23
+
"url": "https://plc.indexx.dev",
24
+
"country": "US",
25
+
"maintainer": "@indexx.dev"
26
+
},
27
+
{
28
+
"url": "https://plc.nyxt.dev"
29
+
},
30
+
{
31
+
"url": "https://plc.madebydanny.uk",
32
+
"country": "US",
33
+
"maintainer": "@madebydanny.uk"
34
+
},
35
+
{
36
+
"url": "https://plc.tartarus.us"
37
+
},
38
+
{
39
+
"url": "https://plcbundle.snek.cc",
40
+
"country": "US",
41
+
"maintainer": "@jackvalinsky.com"
42
+
},
43
+
{
44
+
"url": "https://plc.dane.computer",
45
+
"country": "CA",
46
+
"maintainer": "@dane.is.extraordinarily.cool"
47
+
},
48
+
{
49
+
"url": "https://plc.witchcraft.systems"
50
+
}
51
+
]
+53
src/lib/utils.ts
+53
src/lib/utils.ts
···
1
+
import numeral from 'numeral';
2
+
3
+
export function formatNumber(n: number) {
4
+
return numeral(n).format()
5
+
}
6
+
7
+
export function formatUptime(totalSeconds: number): string {
8
+
if (totalSeconds < 0) {
9
+
return "Invalid input: seconds cannot be negative";
10
+
}
11
+
if (totalSeconds === 0) {
12
+
return "0s";
13
+
}
14
+
15
+
let parts: string[] = [];
16
+
let displayUnits: [string, number][];
17
+
18
+
const SECONDS_IN_MINUTE = 60;
19
+
const SECONDS_IN_HOUR = 3600; // 60 minutes * 60 seconds/minute
20
+
const SECONDS_IN_DAY = 24 * SECONDS_IN_HOUR;
21
+
22
+
if (totalSeconds > SECONDS_IN_DAY) { // If uptime is bigger than 1 day
23
+
displayUnits = [
24
+
['d', SECONDS_IN_DAY],
25
+
['h', SECONDS_IN_HOUR],
26
+
];
27
+
} else if (totalSeconds > SECONDS_IN_MINUTE) { // If uptime is bigger than 60 seconds but not bigger than 60 minutes
28
+
displayUnits = [
29
+
['d', SECONDS_IN_DAY],
30
+
['h', SECONDS_IN_HOUR],
31
+
['m', SECONDS_IN_MINUTE],
32
+
];
33
+
} else { // If uptime is 60 seconds or less
34
+
displayUnits = [
35
+
['d', SECONDS_IN_DAY],
36
+
['h', SECONDS_IN_HOUR],
37
+
['m', SECONDS_IN_MINUTE],
38
+
['s', 1],
39
+
];
40
+
}
41
+
42
+
let remainingSeconds = totalSeconds;
43
+
44
+
for (const [unitChar, unitSeconds] of displayUnits) {
45
+
if (remainingSeconds >= unitSeconds) {
46
+
const value = Math.floor(remainingSeconds / unitSeconds);
47
+
parts.push(`${value}${unitChar}`);
48
+
remainingSeconds %= unitSeconds;
49
+
}
50
+
}
51
+
52
+
return parts.join("");
53
+
}
+1
src/types/lodash.d.ts
+1
src/types/lodash.d.ts
···
1
+
declare module 'lodash/orderBy';