Compare commits

...

5 Commits

Author SHA1 Message Date
54924b39f0 feat: upgrade to SvelteKit 3 and adapter-bun
Some checks failed
Test and deploy staging / staging (push) Failing after 43s
Run the sveltekit-3 migration and finish its manual tasks:

- Move kit config into the sveltekit() Vite plugin; drop svelte.config.js
- Replace $lib with #lib subpath imports
- Declare env vars in src/env.ts and import them from $app/env/{private,public}
- Consolidate param matchers into src/params.ts
- Use error(status, message), Response.json, refreshAll and goto reset/shallow
- Fall back to a full page load when goto rejects a login return path

Switch from adapter-node to adapter-bun: the build script runs Vite under
Bun (the adapter requires it) and the image starts with `bun ./build`.
Runtime env names (PORT, BODY_SIZE_LIMIT, ADDRESS_HEADER, ...) are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:17:30 +00:00
b52e9188f4 house worker 2026-10-11 22:05:05 +00:00
ecd49fc410 fix: parse real Pararius search titles correctly 2026-10-11 21:38:47 +00:00
4ea0ba2d48 fix: enforce house worker request spacing and ignore local secrets 2026-10-11 21:14:41 +00:00
0846cf7d77 feat: house tracker with home worker, alerts and listing map
Collects Pararius rent/buy listings into Postgres via a worker that runs on a
residential connection, sends Discord/ntfy alerts for new listings matching
rules, and shows an admin-only map, market stats and worker log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HfF5tnChi8tkCx9svJP6D
2026-10-11 20:57:20 +00:00
264 changed files with 5795 additions and 895 deletions

View File

@@ -28,3 +28,10 @@ R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY= R2_SECRET_ACCESS_KEY=
R2_BUCKET= R2_BUCKET=
R2_PUBLIC_URL= R2_PUBLIC_URL=
# House tracker: shared secret for the home worker (openssl rand -hex 32), and
# where alerts go (Discord webhook and/or ntfy topic URL). See docs/house-tracker.md.
HOUSE_WORKER_TOKEN=
HOUSE_DISCORD_WEBHOOK_URL=
HOUSE_NTFY_URL=
HOUSE_NTFY_TOKEN=

4
.gitignore vendored
View File

@@ -16,6 +16,10 @@ Thumbs.db
!.env.example !.env.example
!.env.test !.env.test
# Home worker credentials and browser cookies
/house.env
/.house-worker-profile/
# Vite # Vite
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*

View File

@@ -18,3 +18,4 @@ data/cod-challenges.json
# Generated local image download manifest # Generated local image download manifest
data/game-images.json data/game-images.json
data/cod-achievement-art.json data/cod-achievement-art.json
src/lib/house-tracker/fixtures/

View File

@@ -21,4 +21,4 @@ COPY package.json ./
EXPOSE 3000 EXPOSE 3000
ENV NODE_ENV=production ENV NODE_ENV=production
ENV BODY_SIZE_LIMIT=6M ENV BODY_SIZE_LIMIT=6M
CMD ["bun", "build/index.js"] CMD ["bun", "./build"]

View File

@@ -7,10 +7,16 @@ This is my personal website, including:
- Blog posts + comments - Blog posts + comments
- Projects like Animaldex, CatchEmAll - Projects like Animaldex, CatchEmAll
## House tracker
Available at `/projects/house-tracker` (admin only). Listings come from a worker
that runs at home; setup, settings and the ban-avoidance rules are in
[docs/house-tracker.md](docs/house-tracker.md).
## osu! Map Finder ## osu! Map Finder
Available at `/projects/spotify-beatmap-finder`. Built with the existing Svelte 5, Available at `/projects/spotify-beatmap-finder`. Built with the existing Svelte 5,
SvelteKit, Tailwind 4, and adapter-node setup. Uses server-only direct `fetch` SvelteKit 3, Tailwind 4, and adapter-bun setup. Uses server-only direct `fetch`
calls; no Spotify/osu! SDK or additional services. calls; no Spotify/osu! SDK or additional services.
Set these private variables in your local `.env` or deployment environment: Set these private variables in your local `.env` or deployment environment:
@@ -23,7 +29,7 @@ OSU_CLIENT_SECRET
``` ```
Never prefix these with `PUBLIC_` or commit `.env`. Providers read Never prefix these with `PUBLIC_` or commit `.env`. Providers read
`$env/dynamic/private`; the browser receives only display metadata and results. `$app/env/private`; the browser receives only display metadata and results.
The other projects on this personal site retain their own environment requirements. The other projects on this personal site retain their own environment requirements.
Supported inputs: song text (`Deftones Change`), Spotify track, playlist, and album Supported inputs: song text (`Deftones Change`), Spotify track, playlist, and album
@@ -64,7 +70,7 @@ up to six active searches globally, and 12 budget units per IP per 10 minutes:
a single track costs one, a playlist/album six. Expired records are cleaned on a single track costs one, a playlist/album six. Expired records are cleaned on
requests; the limiter has a 10,000-record ceiling. It uses `getClientAddress()`, requests; the limiter has a 10,000-record ceiling. It uses `getClientAddress()`,
never arbitrary forwarded headers; unsupported adapters share a conservative key. never arbitrary forwarded headers; unsupported adapters share a conservative key.
Configure adapter-node's trusted proxy/client-address settings correctly at deployment. Configure adapter-bun's trusted proxy/client-address settings (`ADDRESS_HEADER`, `XFF_DEPTH`) correctly at deployment.
Requests have a 12-second timeout and at most two retries for network errors, Requests have a 12-second timeout and at most two retries for network errors,
429s or server errors. Backoff includes jitter. `Retry-After` is honored; delays 429s or server errors. Backoff includes jitter. `Retry-After` is honored; delays

112
bun.lock
View File

@@ -9,7 +9,6 @@
"@aws-sdk/client-s3": "^3.1121.0", "@aws-sdk/client-s3": "^3.1121.0",
"@aws-sdk/s3-request-presigner": "^3.1121.0", "@aws-sdk/s3-request-presigner": "^3.1121.0",
"@maptiler/sdk": "^4.1.0", "@maptiler/sdk": "^4.1.0",
"@sveltejs/adapter-node": "^5.5.7",
"ai": "^7.0.85", "ai": "^7.0.85",
"d3-geo": "^3.1.1", "d3-geo": "^3.1.1",
"drizzle-orm": "^1.0.0-rc.4", "drizzle-orm": "^1.0.0-rc.4",
@@ -30,8 +29,9 @@
"@internationalized/date": "^3.12.0", "@internationalized/date": "^3.12.0",
"@lucide/svelte": "^1.48.0", "@lucide/svelte": "^1.48.0",
"@playwright/test": "^1.62.1", "@playwright/test": "^1.62.1",
"@sveltejs/adapter-bun": "^1.0.0",
"@sveltejs/enhanced-img": "^0.11.0", "@sveltejs/enhanced-img": "^0.11.0",
"@sveltejs/kit": "^2.70.3", "@sveltejs/kit": "^3.0.0",
"@sveltejs/vite-plugin-svelte": "^7.3.0", "@sveltejs/vite-plugin-svelte": "^7.3.0",
"@tailwindcss/typography": "^0.5.20", "@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
@@ -54,7 +54,7 @@
"prettier": "3.9.6", "prettier": "3.9.6",
"prettier-plugin-svelte": "^4.1.1", "prettier-plugin-svelte": "^4.1.1",
"shadcn-svelte": "^1.7.0", "shadcn-svelte": "^1.7.0",
"svelte": "^5.57.0", "svelte": "^5.57.1",
"svelte-check": "4.7.6", "svelte-check": "4.7.6",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"tailwind-variants": "^3.3.1", "tailwind-variants": "^3.3.1",
@@ -325,8 +325,6 @@
"@melt-ui/svelte": ["@melt-ui/svelte@0.76.2", "", { "dependencies": { "@floating-ui/core": "^1.3.1", "@floating-ui/dom": "^1.4.5", "@internationalized/date": "^3.5.0", "dequal": "^2.0.3", "focus-trap": "^7.5.2", "nanoid": "^5.0.4" }, "peerDependencies": { "svelte": ">=3 <5" } }, "sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA=="], "@melt-ui/svelte": ["@melt-ui/svelte@0.76.2", "", { "dependencies": { "@floating-ui/core": "^1.3.1", "@floating-ui/dom": "^1.4.5", "@internationalized/date": "^3.5.0", "dequal": "^2.0.3", "focus-trap": "^7.5.2", "nanoid": "^5.0.4" }, "peerDependencies": { "svelte": ">=3 <5" } }, "sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA=="],
"@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="],
"@noble/ciphers": ["@noble/ciphers@2.4.0", "", {}, "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw=="], "@noble/ciphers": ["@noble/ciphers@2.4.0", "", {}, "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw=="],
"@noble/hashes": ["@noble/hashes@2.4.0", "", {}, "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA=="], "@noble/hashes": ["@noble/hashes@2.4.0", "", {}, "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA=="],
@@ -371,66 +369,8 @@
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg=="],
"@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="],
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
"@rollup/plugin-replace": ["@rollup/plugin-replace@6.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.4.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-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.4.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-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.63.1", "", { "os": "android", "cpu": "arm" }, "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.63.1", "", { "os": "android", "cpu": "arm64" }, "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.63.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.63.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.63.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.63.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.63.1", "", { "os": "linux", "cpu": "arm" }, "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.63.1", "", { "os": "linux", "cpu": "arm" }, "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.63.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.63.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.63.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.63.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.63.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.63.1", "", { "os": "linux", "cpu": "x64" }, "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.63.1", "", { "os": "linux", "cpu": "x64" }, "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.63.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.63.1", "", { "os": "none", "cpu": "arm64" }, "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.63.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.63.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.63.1", "", { "os": "win32", "cpu": "x64" }, "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.63.1", "", { "os": "win32", "cpu": "x64" }, "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w=="],
"@smithy/core": ["@smithy/core@3.33.3", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg=="], "@smithy/core": ["@smithy/core@3.33.3", "", { "dependencies": { "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="], "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="],
@@ -447,11 +387,11 @@
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.13", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ=="], "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.13", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ=="],
"@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.5.7", "", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.3", "rollup": "^4.59.0" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-uOfc9eVlI3A37RRSaKcgrheBYPrfJwC9VMqDp8x/O6tlKdcLLvHThSWD0KNIbjQ/d+7bwLGx3vx6aowAcRfd2g=="], "@sveltejs/adapter-bun": ["@sveltejs/adapter-bun@1.0.0", "", { "dependencies": { "bun-types": "^1.3.14" }, "peerDependencies": { "@sveltejs/kit": "^3.0.0-next.0" } }, "sha512-CUfFNxrWrKk/0VqSM1DrXCAwpY3tWocaFwBohuBeyxKx07859shLqPdT9oo00lDti9PvVwhQMDS6bKQylTyv0g=="],
"@sveltejs/enhanced-img": ["@sveltejs/enhanced-img@0.11.0", "", { "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-TN7VzGoqwFqvA4Faj1brUPHLtcEkyFa08nrn9bfm98yVrzr0g4bOZHjlBoPYHHX+sZNGE+cxznhTVgarECPQsA=="], "@sveltejs/enhanced-img": ["@sveltejs/enhanced-img@0.11.0", "", { "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-TN7VzGoqwFqvA4Faj1brUPHLtcEkyFa08nrn9bfm98yVrzr0g4bOZHjlBoPYHHX+sZNGE+cxznhTVgarECPQsA=="],
"@sveltejs/kit": ["@sveltejs/kit@2.70.3", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.9", "@types/cookie": "^0.6.0", "acorn": "^8.16.0", "cookie": "^0.6.0", "devalue": "^5.8.1", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3 || ^6.0.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-UDvEYuZqAMbfB/oXIoqKvbKcb7YczK5zYrzmsGV1zRJk03jntwp8dXiYoIJotxAndsKvcPFtx9H1GRSKFdSHgg=="], "@sveltejs/kit": ["@sveltejs/kit@3.1.0", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1", "@standard-schema/spec": "^1.1.0", "cookie": "^2.0.1", "devalue": "^5.9.4", "esm-env": "^1.2.2", "magic-string": "^1.2.3", "mrmime": "^2.0.1", "sirv": "^3.0.2" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^7.0.0", "svelte": "^5.57.1", "typescript": "^6.0.0", "vite": "^8.0.12" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-JTWlq2HQlkqx6vIoxhC0R6sc4WVqbQoRyEmr5YlqKmiYbzY1U3awtSWTmjMLlS056a49AmkQR6rnNANXIE8B0Q=="],
"@sveltejs/load-config": ["@sveltejs/load-config@0.2.3", "", {}, "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ=="], "@sveltejs/load-config": ["@sveltejs/load-config@0.2.3", "", {}, "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ=="],
@@ -493,8 +433,6 @@
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/d3-geo": ["@types/d3-geo@3.1.1", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w=="], "@types/d3-geo": ["@types/d3-geo@3.1.1", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
@@ -513,8 +451,6 @@
"@types/pg": ["@types/pg@8.23.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A=="], "@types/pg": ["@types/pg@8.23.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A=="],
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
"@types/topojson-client": ["@types/topojson-client@3.1.5", "", { "dependencies": { "@types/geojson": "*", "@types/topojson-specification": "*" } }, "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw=="], "@types/topojson-client": ["@types/topojson-client@3.1.5", "", { "dependencies": { "@types/geojson": "*", "@types/topojson-specification": "*" } }, "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw=="],
"@types/topojson-specification": ["@types/topojson-specification@1.0.5", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ=="], "@types/topojson-specification": ["@types/topojson-specification@1.0.5", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ=="],
@@ -631,6 +567,8 @@
"browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": "cli.js" }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": "cli.js" }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="],
"bun-types": ["bun-types@1.4.3", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-tinRJNGOmUN+tEAsn2y2e942n9JHRHR42iUYzDH75NmhJqVBokGSQkkfSFdNWwsANIdrK6ACH9t01M452YyTwg=="],
"caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="], "caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="],
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
@@ -645,13 +583,11 @@
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
"commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], "cookie": ["cookie@2.0.1", "", {}, "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
@@ -673,7 +609,7 @@
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"devalue": ["devalue@5.9.2", "", {}, "sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w=="], "devalue": ["devalue@5.9.4", "", {}, "sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg=="],
"drizzle-kit": ["drizzle-kit@1.0.0-rc.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.12.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-KZCpjRyu+oYHLj/UJogfFlOkWhHVkaEI2EOT1U3NDVXUzLoTyPjqwFxwOrlQxsY6jzRyxcz4EacqboGfhEeYrA=="], "drizzle-kit": ["drizzle-kit@1.0.0-rc.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.12.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-KZCpjRyu+oYHLj/UJogfFlOkWhHVkaEI2EOT1U3NDVXUzLoTyPjqwFxwOrlQxsY6jzRyxcz4EacqboGfhEeYrA=="],
@@ -687,8 +623,6 @@
"enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="],
"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": "bin/esbuild" }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "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": "bin/esbuild" }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
@@ -753,8 +687,6 @@
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="],
"gl-matrix": ["gl-matrix@3.4.4", "", {}, "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ=="], "gl-matrix": ["gl-matrix@3.4.4", "", {}, "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ=="],
@@ -765,8 +697,6 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
"highlight.js": ["highlight.js@11.12.0", "", {}, "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg=="], "highlight.js": ["highlight.js@11.12.0", "", {}, "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -781,14 +711,10 @@
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
"is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
@@ -815,8 +741,6 @@
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "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=="],
"known-css-properties": ["known-css-properties@0.37.0", "", {}, "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ=="], "known-css-properties": ["known-css-properties@0.37.0", "", {}, "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ=="],
"kysely": ["kysely@0.29.5", "", {}, "sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ=="], "kysely": ["kysely@0.29.5", "", {}, "sha512-ooa+eSbBNPTo3MycPEuW5jdrxQdQwdtB3LC3h43FiXQbIry5tR0C5lDG7eealK0E4D7XjrnOP5DIUg/LyjRMYQ=="],
@@ -909,8 +833,6 @@
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pbf": ["pbf@4.0.2", "", { "dependencies": { "resolve-protobuf-schema": "^2.1.0" }, "bin": "bin/pbf" }, "sha512-J0ajxARhZfpUEebxYs1vhMGMuLSXtBe1e+fFPDrf2uA2hgo+UshKfNUWOz92HJNz6/NFEXseQPddnHkTreWRqg=="], "pbf": ["pbf@4.0.2", "", { "dependencies": { "resolve-protobuf-schema": "^2.1.0" }, "bin": "bin/pbf" }, "sha512-J0ajxARhZfpUEebxYs1vhMGMuLSXtBe1e+fFPDrf2uA2hgo+UshKfNUWOz92HJNz6/NFEXseQPddnHkTreWRqg=="],
@@ -971,16 +893,12 @@
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"resolve-protobuf-schema": ["resolve-protobuf-schema@2.1.0", "", { "dependencies": { "protocol-buffers-schema": "^3.3.1" } }, "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ=="], "resolve-protobuf-schema": ["resolve-protobuf-schema@2.1.0", "", { "dependencies": { "protocol-buffers-schema": "^3.3.1" } }, "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ=="],
"rolldown": ["rolldown@1.2.6", "", { "dependencies": { "@oxc-project/types": "=0.147.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.6", "@rolldown/binding-android-arm64": "1.2.6", "@rolldown/binding-darwin-arm64": "1.2.6", "@rolldown/binding-darwin-x64": "1.2.6", "@rolldown/binding-freebsd-x64": "1.2.6", "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", "@rolldown/binding-linux-arm64-gnu": "1.2.6", "@rolldown/binding-linux-arm64-musl": "1.2.6", "@rolldown/binding-linux-ppc64-gnu": "1.2.6", "@rolldown/binding-linux-s390x-gnu": "1.2.6", "@rolldown/binding-linux-x64-gnu": "1.2.6", "@rolldown/binding-linux-x64-musl": "1.2.6", "@rolldown/binding-openharmony-arm64": "1.2.6", "@rolldown/binding-win32-arm64-msvc": "1.2.6", "@rolldown/binding-win32-x64-msvc": "1.2.6" }, "bin": "bin/cli.mjs" }, "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA=="], "rolldown": ["rolldown@1.2.6", "", { "dependencies": { "@oxc-project/types": "=0.147.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.6", "@rolldown/binding-android-arm64": "1.2.6", "@rolldown/binding-darwin-arm64": "1.2.6", "@rolldown/binding-darwin-x64": "1.2.6", "@rolldown/binding-freebsd-x64": "1.2.6", "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", "@rolldown/binding-linux-arm64-gnu": "1.2.6", "@rolldown/binding-linux-arm64-musl": "1.2.6", "@rolldown/binding-linux-ppc64-gnu": "1.2.6", "@rolldown/binding-linux-s390x-gnu": "1.2.6", "@rolldown/binding-linux-x64-gnu": "1.2.6", "@rolldown/binding-linux-x64-musl": "1.2.6", "@rolldown/binding-openharmony-arm64": "1.2.6", "@rolldown/binding-win32-arm64-msvc": "1.2.6", "@rolldown/binding-win32-x64-msvc": "1.2.6" }, "bin": "bin/cli.mjs" }, "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA=="],
"rollup": ["rollup@4.63.1", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.63.1", "@rollup/rollup-android-arm64": "4.63.1", "@rollup/rollup-darwin-arm64": "4.63.1", "@rollup/rollup-darwin-x64": "4.63.1", "@rollup/rollup-freebsd-arm64": "4.63.1", "@rollup/rollup-freebsd-x64": "4.63.1", "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", "@rollup/rollup-linux-arm-musleabihf": "4.63.1", "@rollup/rollup-linux-arm64-gnu": "4.63.1", "@rollup/rollup-linux-arm64-musl": "4.63.1", "@rollup/rollup-linux-loong64-gnu": "4.63.1", "@rollup/rollup-linux-loong64-musl": "4.63.1", "@rollup/rollup-linux-ppc64-gnu": "4.63.1", "@rollup/rollup-linux-ppc64-musl": "4.63.1", "@rollup/rollup-linux-riscv64-gnu": "4.63.1", "@rollup/rollup-linux-riscv64-musl": "4.63.1", "@rollup/rollup-linux-s390x-gnu": "4.63.1", "@rollup/rollup-linux-x64-gnu": "4.63.1", "@rollup/rollup-linux-x64-musl": "4.63.1", "@rollup/rollup-openbsd-x64": "4.63.1", "@rollup/rollup-openharmony-arm64": "4.63.1", "@rollup/rollup-win32-arm64-msvc": "4.63.1", "@rollup/rollup-win32-ia32-msvc": "4.63.1", "@rollup/rollup-win32-x64-gnu": "4.63.1", "@rollup/rollup-win32-x64-msvc": "4.63.1", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg=="],
"rou3": ["rou3@0.9.2", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="], "rou3": ["rou3@0.9.2", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="],
"runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="], "runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="],
@@ -1011,9 +929,7 @@
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "svelte": ["svelte@5.57.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "@jridgewell/sourcemap-codec": "^1.6.0", "@sveltejs/acorn-typescript": "^1.0.13", "@types/estree": "^1.0.9", "acorn": "^8.18.0", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.9.2", "esm-env": "^1.2.1", "esrap": "^2.3.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-1YiPCAXT0Ct6g1vOMmSLa0Nsc3QRMvtDF32ihgglGVrugIuIK38RyDiFNXVDhtQ81TJjSO57NdMw6DyYLqSGDQ=="],
"svelte": ["svelte@5.57.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@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.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.12", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-NdbDn7fl4be1ViUG0oq/lvG6OZy3oENolV2ONjiqqsfVoeAfzaQAKUcEX3MrQod/Bebv1PgwET9rfXhgn9s4Kg=="],
"svelte-check": ["svelte-check@4.7.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "^0.2.3", "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 || ^6.0.0" }, "bin": "bin/svelte-check" }, "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw=="], "svelte-check": ["svelte-check@4.7.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "^0.2.3", "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 || ^6.0.0" }, "bin": "bin/svelte-check" }, "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw=="],
@@ -1111,12 +1027,10 @@
"@maplibre/vt-pbf/pbf": ["pbf@5.1.2", "", { "dependencies": { "resolve-protobuf-schema": "^2.1.0" }, "bin": "bin/pbf" }, "sha512-mnvGdvOrIvJOBGUEdGkrVXjN8E/VkIJCkf2eS1DH2yv82ORUlLttmDt0rWY38yYZmVwciZwBUvHM20qxBZf40w=="], "@maplibre/vt-pbf/pbf": ["pbf@5.1.2", "", { "dependencies": { "resolve-protobuf-schema": "^2.1.0" }, "bin": "bin/pbf" }, "sha512-mnvGdvOrIvJOBGUEdGkrVXjN8E/VkIJCkf2eS1DH2yv82ORUlLttmDt0rWY38yYZmVwciZwBUvHM20qxBZf40w=="],
"@rollup/plugin-commonjs/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@sveltejs/kit/magic-string": ["magic-string@1.2.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g=="],
"@sveltejs/vite-plugin-svelte/magic-string": ["magic-string@1.2.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g=="], "@sveltejs/vite-plugin-svelte/magic-string": ["magic-string@1.2.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g=="],
"@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
@@ -1151,8 +1065,6 @@
"postcss/nanoid": ["nanoid@3.3.18", "", { "bin": "bin/nanoid.cjs" }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], "postcss/nanoid": ["nanoid@3.3.18", "", { "bin": "bin/nanoid.cjs" }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
"rollup/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"shadcn-svelte/cn": ["cn@0.3.3", "", { "bin": { "cn": "bin/cn.mjs" } }, "sha512-OEiyJkSpYm2kOuNOKQ+c2wmzZRG8orrb9fnObtHTbCGNx/E9KLnGcmcocItOzREKk5KzQN5I2e3BUCj563KEpw=="], "shadcn-svelte/cn": ["cn@0.3.3", "", { "bin": { "cn": "bin/cn.mjs" } }, "sha512-OEiyJkSpYm2kOuNOKQ+c2wmzZRG8orrb9fnObtHTbCGNx/E9KLnGcmcocItOzREKk5KzQN5I2e3BUCj563KEpw=="],
"svelte-eslint-parser/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], "svelte-eslint-parser/eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],

178
docs/house-tracker.md Normal file
View File

@@ -0,0 +1,178 @@
# House tracker
Personal tool: collects Dutch housing listings into Postgres, shows them on
`/projects/house-tracker` (admin only) and sends an alert when a new listing
matches a rule. Only Pararius for now; other sources plug in the same way.
## How it fits together
```
home box (residential IP) stanrunge.dev (Hetzner)
┌──────────────────────────┐ POST sync ┌──────────────────────────────┐
│ scripts/house-worker.ts │ ──────────────▶ │ /projects/house-tracker/api/ │
│ polls pararius.nl │ POST details │ worker/{sync,details,report}│
│ parses HTML │ ◀────────────── │ stores, dedups, alerts │
└──────────────────────────┘ needDetails └──────────────┬───────────────┘
▼
Discord webhook / ntfy push
```
The server never talks to the listing sites. Pararius sits behind Cloudflare,
which challenges datacenter IPs and plain HTTP clients, so the worker runs at
home where traffic looks like a normal visitor.
Two lanes:
- **fast**: one filtered search (default `huurwoningen/gouda/1300-1700`), page 1
only. Every 3–5 min on weekdays 07:00–18:00 Amsterdam time, every 30–60 min
otherwise. Listings near Gouda were all published in office hours on weekdays.
Alerts are sent from the search card alone, before the detail page is fetched.
- **slow**: full, paginated sweeps of rent and buy listings in Gouda, Rotterdam,
Den Haag, Utrecht, Amsterdam and Eindhoven every 12 h. Unfiltered city sweeps
also mark listings that disappeared, which gives "days listed" in the stats.
Safety rules in the worker: one request at a time, at least 8 s apart with
jitter, a daily budget (1500 by default, the slow lane may only use 60%), and
any challenge/403/429 stops everything and backs off 15 min → 30 → 60 … 6 h.
The first poll of any search only seeds the database, so you don't get 40
alerts for listings that were already online.
## Server setup
Add to the production env file (`/etc/personal-website/prod.env`):
```text
# openssl rand -hex 32
HOUSE_WORKER_TOKEN=
# one or both
HOUSE_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
HOUSE_NTFY_URL=https://ntfy.sh/some-long-random-topic
HOUSE_NTFY_TOKEN= # only for a protected ntfy topic
PUBLIC_MAPTILER_API_KEY= # already set for Animaldex
```
`scripts/migrate-house-tracker.ts` runs on every deploy (idempotent). Use
"send test alert" on the page to check the alert channel.
If Cloudflare Bot Fight Mode / WAF challenges are on for stanrunge.dev, add a
WAF skip rule for `URI Path starts with /projects/house-tracker/api/worker/`,
otherwise the worker's POSTs get challenged too. The endpoints are protected by
the bearer token.
### Development behind Cloudflare Access
For `https://dev.stanrunge.dev`, keep Access enabled. In Cloudflare Zero Trust,
go to **Access controls → Service credentials → Service Tokens** and create a
token named `house-worker`. Save its Client ID and Client Secret. In the Access
application protecting the dev hostname, add a **Service Auth** policy with
**Include → Service Token → house-worker**. Keep the existing browser login
policy. See [Cloudflare's service token documentation](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/).
Set `HOUSE_WORKER_TOKEN` in the dev website's `.env` and restart its dev server.
On the home machine, copy `house.env.example` to `house.env`, fill in the same
worker token plus the Cloudflare Client ID and Client Secret, then run:
```sh
chmod 600 house.env
bun --env-file=house.env scripts/house-worker.ts --check-api
```
This makes one authenticated GET to the website's worker health endpoint. It
does not contact Pararius, sync listings or send alerts. The expected output is
`API connection OK`. Access credentials are sent only to the website API; the
client refuses redirects and stops immediately on an authentication failure.
An HTTP 503 can mean the website's worker token is missing; an HTTP 404 can mean
the running website does not yet include the health route.
## Home worker setup
Needs a machine that stays on (old laptop, Raspberry Pi, NAS) with Bun 1.4 or
newer; older versions cannot read this repository's lockfile. Use `bun upgrade`
if needed.
```sh
git clone https://git.stanrunge.dev/stan/personal-website ~/house-worker
cd ~/house-worker && bun install --frozen-lockfile
cat > house.env <<'EOF'
HOUSE_API_URL=https://stanrunge.dev
HOUSE_WORKER_TOKEN=same-value-as-the-server
EOF
chmod 600 house.env
# 1. check parsing without sending anything
bun --env-file=house.env scripts/house-worker.ts --dry-run --once
# 2. one real poll
bun --env-file=house.env scripts/house-worker.ts --once
```
Then run it as a service, `/etc/systemd/system/house-worker.service`:
```ini
[Unit]
Description=House tracker worker
After=network-online.target
Wants=network-online.target
[Service]
User=stan
WorkingDirectory=/home/stan/house-worker
ExecStart=/home/stan/.bun/bin/bun --env-file=house.env scripts/house-worker.ts
Restart=always
RestartSec=60
[Install]
WantedBy=multi-user.target
```
```sh
sudo systemctl enable --now house-worker
journalctl -u house-worker -f
```
Or with Docker: `docker run -d --restart unless-stopped --name house-worker
--env-file house.env -v "$PWD":/app -w /app oven/bun:1.4.0 bun
scripts/house-worker.ts`.
## Worker settings
| variable | default |
| ------------------------ | ------------------------------------------------------------------------ |
| `HOUSE_FAST_SEARCHES` | `https://www.pararius.nl/huurwoningen/gouda/1300-1700` (comma separated) |
| `HOUSE_SLOW_SEARCHES` | rent + buy for the six cities above |
| `HOUSE_SLOW_EVERY_HOURS` | `12` |
| `HOUSE_SLOW_MAX_PAGES` | `80` per search |
| `HOUSE_MIN_GAP_SECONDS` | `8` between any two requests |
| `HOUSE_DAILY_BUDGET` | `1500` requests |
| `HOUSE_FETCH_MODE` | `fetch`, or `browser` (real Chromium) |
Any Pararius search URL works as a fast search: set the filters on the site
(price, radius, bedrooms) and paste the URL. Every extra fast search is one more
request per poll.
## When it gets challenged
You get one "worker was challenged" alert per 6 hours and the page header turns
red. In order:
1. Do nothing for a day. Backoff handles short blocks.
2. Slow down: raise `HOUSE_MIN_GAP_SECONDS`, lower `HOUSE_DAILY_BUDGET`, or drop
slow searches.
3. Save a page manually in your normal browser to investigate parsing offline.
Do not switch clients, use proxies or solve challenges to bypass a block.
The optional `HOUSE_FETCH_MODE=browser` uses headless Chromium with a persistent
profile (`npx playwright install chromium` once on the worker machine). It obeys
the same stop/backoff rules and is not a workaround for a challenged fetch.
## Parsers
`src/lib/house-tracker/pararius.ts`, tested against trimmed real pages in
`src/lib/house-tracker/fixtures/`. Listing pages expose an exact publish
timestamp (`odp.offered_since`), coordinates, deposit, energy label and agent.
`pararius-search-gouda.html` is a trimmed real search page saved on 2026-10-11,
with tracking and account data removed. It covers three listings, nested base
and total rent, action attributes and a lazy photo template. The synthetic
fixture still covers pagination and price on request. Rental `priceEur` is the
base/listed rent used by the search filter; a separately shown total can be
higher (Raam: €1,475 base, €1,750 total). If Pararius changes markup, the worker
reports `parse-error` in the worker log on the page.

View File

@@ -7,6 +7,6 @@ export default defineConfig({
verbose: true, verbose: true,
strict: true, strict: true,
dbCredentials: { dbCredentials: {
url: process.env.DB_URL url: process.env.DB_URL!
} }
}); });

7
house.env.example Normal file
View File

@@ -0,0 +1,7 @@
# Copy to house.env on the HOME machine. That filename is ignored by Git.
HOUSE_API_URL=https://dev.stanrunge.dev
# Same value as HOUSE_WORKER_TOKEN in the website's .env.
HOUSE_WORKER_TOKEN=
# Cloudflare Access service token, used only for requests to HOUSE_API_URL.
HOUSE_CF_ACCESS_CLIENT_ID=
HOUSE_CF_ACCESS_CLIENT_SECRET=

View File

@@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "bun --bun vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "npm run test:integration && npm run test:unit", "test": "npm run test:integration && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsgo --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsgo --tsconfig ./tsconfig.json",
@@ -25,6 +25,8 @@
"db:migrate:game-tracker": "bun scripts/migrate-game-tracker.ts", "db:migrate:game-tracker": "bun scripts/migrate-game-tracker.ts",
"db:migrate:cs-prices": "bun scripts/migrate-cs-prices.ts", "db:migrate:cs-prices": "bun scripts/migrate-cs-prices.ts",
"cs-prices:ingest": "bun scripts/ingest-cs-prices.ts", "cs-prices:ingest": "bun scripts/ingest-cs-prices.ts",
"db:migrate:house-tracker": "bun scripts/migrate-house-tracker.ts",
"house:worker": "bun scripts/house-worker.ts",
"auth:role": "bun scripts/set-admin.ts", "auth:role": "bun scripts/set-admin.ts",
"catalog:build": "python3 scripts/build-pokemon-catalog.py", "catalog:build": "python3 scripts/build-pokemon-catalog.py",
"catalog:cod": "python3 scripts/build-cod-catalog.py && prettier --write data/call-of-duty.json", "catalog:cod": "python3 scripts/build-cod-catalog.py && prettier --write data/call-of-duty.json",
@@ -47,8 +49,9 @@
"@internationalized/date": "^3.12.0", "@internationalized/date": "^3.12.0",
"@lucide/svelte": "^1.48.0", "@lucide/svelte": "^1.48.0",
"@playwright/test": "^1.62.1", "@playwright/test": "^1.62.1",
"@sveltejs/adapter-bun": "^1.0.0",
"@sveltejs/enhanced-img": "^0.11.0", "@sveltejs/enhanced-img": "^0.11.0",
"@sveltejs/kit": "^2.70.3", "@sveltejs/kit": "^3.0.0",
"@sveltejs/vite-plugin-svelte": "^7.3.0", "@sveltejs/vite-plugin-svelte": "^7.3.0",
"@tailwindcss/typography": "^0.5.20", "@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.3", "@tailwindcss/vite": "^4.3.3",
@@ -71,7 +74,7 @@
"prettier": "3.9.6", "prettier": "3.9.6",
"prettier-plugin-svelte": "^4.1.1", "prettier-plugin-svelte": "^4.1.1",
"shadcn-svelte": "^1.7.0", "shadcn-svelte": "^1.7.0",
"svelte": "^5.57.0", "svelte": "^5.57.1",
"svelte-check": "4.7.6", "svelte-check": "4.7.6",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"tailwind-variants": "^3.3.1", "tailwind-variants": "^3.3.1",
@@ -89,7 +92,6 @@
"@aws-sdk/client-s3": "^3.1121.0", "@aws-sdk/client-s3": "^3.1121.0",
"@aws-sdk/s3-request-presigner": "^3.1121.0", "@aws-sdk/s3-request-presigner": "^3.1121.0",
"@maptiler/sdk": "^4.1.0", "@maptiler/sdk": "^4.1.0",
"@sveltejs/adapter-node": "^5.5.7",
"ai": "^7.0.85", "ai": "^7.0.85",
"d3-geo": "^3.1.1", "d3-geo": "^3.1.1",
"drizzle-orm": "^1.0.0-rc.4", "drizzle-orm": "^1.0.0-rc.4",
@@ -102,5 +104,9 @@
"topojson-client": "^3.1.0", "topojson-client": "^3.1.0",
"world-atlas": "^2.0.2", "world-atlas": "^2.0.2",
"zod": "^4.5.4" "zod": "^4.5.4"
},
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
} }
} }

View File

@@ -68,6 +68,7 @@ run_database_script scripts/migrate-foundation.ts
run_database_script scripts/migrate-money-tracker.ts run_database_script scripts/migrate-money-tracker.ts
run_database_script scripts/migrate-game-tracker.ts run_database_script scripts/migrate-game-tracker.ts
run_database_script scripts/migrate-tracker-journal.ts run_database_script scripts/migrate-tracker-journal.ts
run_database_script scripts/migrate-house-tracker.ts
run_database_script scripts/seed-catalog.ts run_database_script scripts/seed-catalog.ts
run_database_script scripts/seed-cod-catalog.ts run_database_script scripts/seed-cod-catalog.ts
run_database_script scripts/seed-pokemon-research.ts run_database_script scripts/seed-pokemon-research.ts

View File

@@ -0,0 +1,111 @@
-- House tracker: listings from Dutch housing sites, collected by a home worker.
-- Idempotent; safe to run on every deploy.
CREATE TABLE IF NOT EXISTS house_listings (
id serial PRIMARY KEY,
source text NOT NULL,
source_id text NOT NULL,
external_id text,
url text NOT NULL,
offer text NOT NULL CHECK (offer IN ('rent', 'buy')),
property_type text NOT NULL,
title text,
street text,
postal_code text,
city text,
city_slug text,
neighbourhood text,
price_eur integer CHECK (price_eur IS NULL OR price_eur >= 0),
area_m2 integer,
rooms integer,
bedrooms integer,
bathrooms integer,
construction_year integer,
energy_label text,
interior text,
deposit_eur integer,
available_from text,
contract_type text,
agent text,
image_url text,
description text,
features jsonb NOT NULL DEFAULT '{}'::jsonb,
lat double precision,
lng double precision,
-- When the site says it was published. first_seen_at is when we noticed.
offered_at timestamptz,
first_seen_at timestamptz NOT NULL DEFAULT now(),
-- fast, slow, or seed (already listed when its search was first polled).
first_seen_lane text NOT NULL DEFAULT 'slow',
last_seen_at timestamptz NOT NULL DEFAULT now(),
detail_fetched_at timestamptz,
removed_at timestamptz,
alerted_at timestamptz,
-- Personal tracking.
status text,
notes text
);
CREATE UNIQUE INDEX IF NOT EXISTS house_listings_source_idx ON house_listings (source, source_id);
CREATE INDEX IF NOT EXISTS house_listings_active_idx
ON house_listings (offer, first_seen_at DESC) WHERE removed_at IS NULL;
CREATE INDEX IF NOT EXISTS house_listings_city_idx ON house_listings (source, offer, city_slug);
CREATE INDEX IF NOT EXISTS house_listings_details_idx
ON house_listings (first_seen_at DESC) WHERE detail_fetched_at IS NULL;
-- Every price we have seen, so price drops and market trends survive edits.
CREATE TABLE IF NOT EXISTS house_price_history (
listing_id integer NOT NULL REFERENCES house_listings(id) ON DELETE CASCADE,
observed_at timestamptz NOT NULL DEFAULT now(),
price_eur integer NOT NULL
);
CREATE INDEX IF NOT EXISTS house_price_history_listing_idx
ON house_price_history (listing_id, observed_at);
-- One row per polled search URL. The first sync of a search only seeds the
-- database: alerting on everything already listed would be noise.
CREATE TABLE IF NOT EXISTS house_searches (
search_url text PRIMARY KEY,
source text NOT NULL,
lane text NOT NULL,
first_synced_at timestamptz NOT NULL DEFAULT now(),
last_synced_at timestamptz NOT NULL DEFAULT now(),
last_found integer NOT NULL DEFAULT 0
);
-- Worker health: challenges, HTTP errors, parse failures.
CREATE TABLE IF NOT EXISTS house_worker_runs (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
source text NOT NULL,
lane text NOT NULL,
url text NOT NULL,
status text NOT NULL,
http_status integer,
found integer,
duration_ms integer,
message text,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS house_worker_runs_created_idx ON house_worker_runs (created_at DESC);
CREATE TABLE IF NOT EXISTS house_alert_rules (
id serial PRIMARY KEY,
name text NOT NULL,
enabled boolean NOT NULL DEFAULT true,
offer text NOT NULL DEFAULT 'rent' CHECK (offer IN ('rent', 'buy')),
min_price integer,
max_price integer,
min_area integer,
min_bedrooms integer,
-- Lowercase city names; empty means anywhere.
cities text[] NOT NULL DEFAULT '{}',
-- Empty means every property type.
property_types text[] NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO house_alert_rules (name, offer, min_price, max_price, cities)
SELECT 'Gouda area rentals', 'rent', 1300, 1700, ARRAY[
'gouda', 'waddinxveen', 'reeuwijk', 'moordrecht', 'nieuwerkerk aan den ijssel',
'zevenhuizen', 'bodegraven', 'boskoop', 'oudewater', 'schoonhoven', 'haastrecht'
]
WHERE NOT EXISTS (SELECT 1 FROM house_alert_rules);

382
scripts/house-worker.ts Normal file
View File

@@ -0,0 +1,382 @@
/**
* House tracker home worker. Runs on a machine at home (residential IP), polls
* Pararius politely and POSTs what it finds to the website, which stores it and
* sends the alerts. See docs/house-tracker.md.
*
* HOUSE_API_URL=https://stanrunge.dev HOUSE_WORKER_TOKEN=... bun scripts/house-worker.ts
* bun scripts/house-worker.ts --once # one fast-lane poll, then exit
* bun scripts/house-worker.ts --dry-run # fetch + parse, print, send nothing
* bun scripts/house-worker.ts --check-api # check website auth only, no Pararius requests
*
* Politeness rules, in order of importance:
* - one request at a time, at least HOUSE_MIN_GAP_SECONDS apart (with jitter)
* - a daily request budget
* - any challenge / 403 / 429 stops everything and backs off exponentially
* - the fast lane only polls often on weekdays 07:00–18:00 Amsterdam time
*/
import {
isChallenge,
parseDetailPage,
parseSearchPage,
searchPageUrl
} from '../src/lib/house-tracker/pararius';
import { backoffMs, nextDelayMs } from '../src/lib/house-tracker/schedule';
import { createWorkerApi } from '../src/lib/house-tracker/worker-api';
import type {
Lane,
ListingDetail,
ReportRequest,
RunStatus,
SyncRequest,
SyncResponse
} from '../src/lib/house-tracker/types';
const args = new Set(process.argv.slice(2));
const once = args.has('--once');
const checkApi = args.has('--check-api');
const dryRun = args.has('--dry-run') || process.env.HOUSE_DRY_RUN === '1';
const list = (value: string | undefined, fallback: string[]) =>
value
? value
.split(/[\s,]+/)
.map((item) => item.trim())
.filter(Boolean)
: fallback;
const number = (value: string | undefined, fallback: number) =>
value && Number.isFinite(Number(value)) ? Number(value) : fallback;
const config = {
api: (process.env.HOUSE_API_URL ?? '').replace(/\/$/, ''),
token: process.env.HOUSE_WORKER_TOKEN ?? '',
fastSearches: list(process.env.HOUSE_FAST_SEARCHES, [
'https://www.pararius.nl/huurwoningen/gouda/1300-1700'
]),
slowSearches: list(
process.env.HOUSE_SLOW_SEARCHES,
['gouda', 'rotterdam', 'den-haag', 'utrecht', 'amsterdam', 'eindhoven'].flatMap((city) => [
`https://www.pararius.nl/huurwoningen/${city}`,
`https://www.pararius.nl/koopwoningen/${city}`
])
),
slowEveryHours: number(process.env.HOUSE_SLOW_EVERY_HOURS, 12),
slowMaxPages: number(process.env.HOUSE_SLOW_MAX_PAGES, 80),
minGapMs: Math.max(8, number(process.env.HOUSE_MIN_GAP_SECONDS, 8)) * 1000,
dailyBudget: number(process.env.HOUSE_DAILY_BUDGET, 1500),
fetchMode: (process.env.HOUSE_FETCH_MODE ?? 'fetch') as 'fetch' | 'browser',
userAgent:
process.env.HOUSE_USER_AGENT ??
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
};
if (checkApi && dryRun) throw new Error('--check-api cannot be combined with dry-run mode');
const requestApi = dryRun
? null
: createWorkerApi(
{
url: config.api,
token: config.token,
accessClientId: process.env.HOUSE_CF_ACCESS_CLIENT_ID,
accessClientSecret: process.env.HOUSE_CF_ACCESS_CLIENT_SECRET
},
checkApi ? 1 : 4
);
const SOURCE = 'pararius';
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const log = (...parts: unknown[]) => console.log(new Date().toISOString(), ...parts);
// ---------------------------------------------------------------------------
// Polite fetching
class Challenged extends Error {
constructor(
readonly url: string,
readonly status: number
) {
super(`Challenged (${status}) on ${url}`);
}
}
const cookies = new Map<string, string>();
let lastRequestAt = 0;
let budgetDay = '';
let budgetUsed = 0;
function remember(response: Response) {
for (const header of response.headers.getSetCookie?.() ?? []) {
const [pair] = header.split(';');
const index = pair.indexOf('=');
if (index > 0) cookies.set(pair.slice(0, index).trim(), pair.slice(index + 1).trim());
}
}
function resetBudget() {
const day = new Date().toISOString().slice(0, 10);
if (day !== budgetDay) {
budgetDay = day;
budgetUsed = 0;
}
}
/** The slow lane may only use part of the budget so the fast lane never runs dry. */
function slowBudgetLeft() {
resetBudget();
return budgetUsed < config.dailyBudget * 0.6;
}
async function waitForTurn() {
resetBudget();
if (budgetUsed >= config.dailyBudget) throw new Error('Daily request budget used up');
const gap = config.minGapMs * (1 + Math.random() * 0.5);
const wait = lastRequestAt + gap - Date.now();
if (wait > 0) await sleep(wait);
lastRequestAt = Date.now();
budgetUsed++;
}
type BrowserPage = {
goto(url: string, options: object): Promise<{ status(): number } | null>;
content(): Promise<string>;
};
let browserPage: Promise<BrowserPage> | null = null;
async function openBrowser(): Promise<BrowserPage> {
// Real Chromium for when plain requests get challenged. Needs
// `npx playwright install chromium` on the worker machine.
const { chromium } = await import('@playwright/test');
const context = await chromium.launchPersistentContext(
process.env.HOUSE_BROWSER_PROFILE ?? '.house-worker-profile',
{ headless: true, locale: 'nl-NL', timezoneId: 'Europe/Amsterdam' }
);
return context.pages()[0] ?? (await context.newPage());
}
async function getHtml(url: string, referer?: string): Promise<{ html: string; status: number }> {
await waitForTurn();
if (config.fetchMode === 'browser') {
browserPage ??= openBrowser();
const page = await browserPage;
const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45_000 });
const html = await page.content();
const status = response?.status() ?? 0;
if (isChallenge(html, status)) throw new Challenged(url, status);
return { html, status };
}
const response = await fetch(url, {
redirect: 'follow',
signal: AbortSignal.timeout(30_000),
headers: {
'user-agent': config.userAgent,
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'accept-language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7',
'cache-control': 'no-cache',
...(referer ? { referer } : {}),
...(cookies.size
? { cookie: [...cookies].map(([key, value]) => `${key}=${value}`).join('; ') }
: {})
}
});
remember(response);
const html = await response.text();
if (isChallenge(html, response.status)) throw new Challenged(url, response.status);
return { html, status: response.status };
}
// ---------------------------------------------------------------------------
// Website API
async function api<T>(path: 'sync' | 'details' | 'report', body: unknown): Promise<T | null> {
return requestApi ? requestApi<T>(path, body) : null;
}
async function report(
lane: Lane,
url: string,
status: RunStatus,
extra: Partial<ReportRequest> = {}
) {
try {
await api('report', { source: SOURCE, lane, url, status, ...extra });
} catch (error) {
log('Could not report:', (error as Error).message);
}
}
// ---------------------------------------------------------------------------
// Lanes
async function fetchDetails(
lane: Lane,
wanted: { sourceId: string; url: string }[],
referer: string
) {
const details: ListingDetail[] = [];
for (const item of wanted) {
const { html } = await getHtml(item.url, referer);
const detail = parseDetailPage(html, item.url);
if (detail) details.push(detail);
else await report(lane, item.url, 'parse-error', { message: 'listing page did not parse' });
}
if (dryRun) {
for (const detail of details) {
log('detail', { ...detail, description: undefined, features: undefined });
}
}
if (details.length) await api('details', { source: SOURCE, details });
}
async function pollSearch(
lane: Lane,
url: string,
completedSweep?: SyncRequest['completedSweep']
): Promise<{ nextPageUrl: string | null; found: number }> {
const startedAt = new Date().toISOString();
const { html } = await getHtml(url);
const page = parseSearchPage(html, url);
if (!page.cards.length && page.nextPageUrl)
await report(lane, url, 'parse-error', {
message: 'no listing cards on a page with a next page'
});
if (dryRun) {
log(`${lane} ${url}: ${page.cards.length} cards, next=${page.nextPageUrl}`);
for (const card of page.cards.slice(0, 5)) log('card', card);
}
const sync = await api<SyncResponse>('sync', {
source: SOURCE,
lane,
searchUrl: url,
startedAt,
cards: page.cards,
completedSweep: page.nextPageUrl ? null : (completedSweep ?? null)
} satisfies SyncRequest);
if (sync) {
if (sync.newIds.length || sync.alerted)
log(`${lane} ${url}: ${sync.newIds.length} new, ${sync.alerted} alerted`);
if (sync.needDetails.length) await fetchDetails(lane, sync.needDetails, url);
} else if (dryRun && page.cards[0]) {
await fetchDetails(lane, [page.cards[0]], url);
}
return { nextPageUrl: page.nextPageUrl, found: page.cards.length };
}
async function fastTick() {
for (const url of config.fastSearches) await pollSearch('fast', url);
}
/** Only an unfiltered `/huurwoningen/<city>` sweep can tell us what disappeared. */
function sweepScope(url: string) {
const [kind, city, ...rest] = new URL(url).pathname.split('/').filter(Boolean);
if (rest.length || !city) return null;
if (kind === 'huurwoningen') return { offer: 'rent' as const, city };
if (kind === 'koopwoningen') return { offer: 'buy' as const, city };
return null;
}
type Sweep = { index: number; page: number; startedAt: string };
let sweep: Sweep | null = null;
/** One page of the slow lane, so fast polls can interleave. Returns false when idle. */
async function slowStep(): Promise<boolean> {
if (!sweep || !slowBudgetLeft()) return false;
const base = config.slowSearches[sweep.index];
if (!base) {
log('Slow sweep finished');
sweep = null;
return false;
}
const url = searchPageUrl(base, sweep.page);
const scope = sweepScope(base);
const result = await pollSearch(
'slow',
url,
scope ? { ...scope, sweepStartedAt: sweep.startedAt } : null
);
if (result.nextPageUrl && sweep.page < config.slowMaxPages) {
sweep.page++;
} else {
sweep = { index: sweep.index + 1, page: 1, startedAt: new Date().toISOString() };
}
return true;
}
// ---------------------------------------------------------------------------
// Main loop
let stopping = false;
process.on('SIGTERM', () => (stopping = true));
process.on('SIGINT', () => (stopping = true));
async function guarded(
lane: Lane,
step: () => Promise<boolean | void>
): Promise<'ok' | 'idle' | 'failed'> {
const started = Date.now();
try {
return (await step()) === false ? 'idle' : 'ok';
} catch (error) {
if (error instanceof Challenged) {
log(`CHALLENGED on ${error.url} (${error.status})`);
await report(lane, error.url, 'challenged', {
httpStatus: error.status,
durationMs: Date.now() - started
});
} else {
log(`${lane} failed:`, (error as Error).message);
await report(lane, config.fastSearches[0], 'network-error', {
message: (error as Error).message.slice(0, 500),
durationMs: Date.now() - started
});
}
return 'failed';
}
}
async function main() {
if (checkApi) {
const result = await requestApi!<{ ok: boolean }>('health');
if (result?.ok !== true) throw new Error('API health: unexpected response');
log('API connection OK. Worker authentication passed; no Pararius requests were made.');
return;
}
log(
`house worker: ${config.fastSearches.length} fast, ${config.slowSearches.length} slow searches, mode=${config.fetchMode}${dryRun ? ', dry run' : ''}`
);
if (once) {
const result = await guarded('fast', fastTick);
process.exit(result === 'ok' ? 0 : 1);
}
let failures = 0;
let nextFast = Date.now();
let nextSlow = Date.now() + 5 * 60_000; // let the fast lane go first
while (!stopping) {
const now = Date.now();
let result: 'ok' | 'idle' | 'failed' = 'idle';
if (now >= nextFast) {
result = await guarded('fast', fastTick);
nextFast = Date.now() + nextDelayMs(new Date());
} else if (sweep || now >= nextSlow) {
if (!sweep) {
log('Slow sweep starting');
sweep = { index: 0, page: 1, startedAt: new Date().toISOString() };
nextSlow = now + config.slowEveryHours * 60 * 60_000;
}
result = await guarded('slow', slowStep);
}
if (result === 'failed') {
failures++;
const pause = backoffMs(failures);
log(`Backing off ${Math.round(pause / 60_000)} min (failure ${failures})`);
nextFast = Math.max(nextFast, Date.now() + pause);
await sleep(pause);
continue;
}
if (result === 'ok') failures = 0;
else await sleep(Math.min(30_000, Math.max(5_000, nextFast - Date.now())));
}
log('Stopped');
process.exit(0);
}
await main();

View File

@@ -0,0 +1,15 @@
import { readFile } from 'node:fs/promises';
import postgres from 'postgres';
if (!process.env.DB_URL) throw new Error('DB_URL is required');
const client = postgres(process.env.DB_URL, { max: 1 });
try {
await client.begin(async (tx) => {
await tx.unsafe(
await readFile(new URL('./house-tracker-migration.sql', import.meta.url), 'utf8')
);
});
console.log('House tracker migration applied');
} finally {
await client.end();
}

6
src/app.d.ts vendored
View File

@@ -6,12 +6,12 @@ declare global {
namespace App { namespace App {
// interface Error {} // interface Error {}
interface Locals { interface Locals {
user: typeof import('$lib/server/auth').auth.$Infer.Session.user | null; user: typeof import('#lib/server/auth.js').auth.$Infer.Session.user | null;
session: typeof import('$lib/server/auth').auth.$Infer.Session.session | null; session: typeof import('#lib/server/auth.js').auth.$Infer.Session.session | null;
} }
// interface PageData {} // interface PageData {}
interface PageState { interface PageState {
animal?: import('$lib/animaldex/AnimalDetails.svelte').AnimalDetail; animal?: import('#lib/animaldex/AnimalDetails.svelte').AnimalDetail;
} }
// interface Platform {} // interface Platform {}
} }

27
src/env.ts Normal file
View File

@@ -0,0 +1,27 @@
import { defineEnvVars } from '@sveltejs/kit/env';
// Every variable is optional and falls back to '' (falsy, like an unset $env/dynamic
// value used to be); the code that uses each one checks whether it is configured.
export const variables = defineEnvVars({
PUBLIC_MAPTILER_API_KEY: { public: true, schema: (input) => input ?? '' },
R2_PUBLIC_URL: { schema: (input) => input ?? '' },
ORIGIN: { schema: (input) => input ?? '' },
BETTER_AUTH_SECRET: { schema: (input) => input ?? '' },
BETTER_AUTH_URL: { schema: (input) => input ?? '' },
GOOGLE_CLIENT_ID: { schema: (input) => input ?? '' },
GOOGLE_CLIENT_SECRET: { schema: (input) => input ?? '' },
OSU_CLIENT_ID: { schema: (input) => input ?? '' },
OSU_CLIENT_SECRET: { schema: (input) => input ?? '' },
ANTHROPIC_API_KEY: { schema: (input) => input ?? '' },
HOUSE_WORKER_TOKEN: { schema: (input) => input ?? '' },
HOUSE_DISCORD_WEBHOOK_URL: { schema: (input) => input ?? '' },
HOUSE_NTFY_URL: { schema: (input) => input ?? '' },
HOUSE_NTFY_TOKEN: { schema: (input) => input ?? '' },
R2_ACCESS_KEY_ID: { schema: (input) => input ?? '' },
R2_SECRET_ACCESS_KEY: { schema: (input) => input ?? '' },
R2_ACCOUNT_ID: { schema: (input) => input ?? '' },
R2_BUCKET: { schema: (input) => input ?? '' },
SPOTIFY_CLIENT_ID: { schema: (input) => input ?? '' },
SPOTIFY_CLIENT_SECRET: { schema: (input) => input ?? '' },
DB_URL: { schema: (input) => input ?? '' }
});

View File

@@ -1,6 +1,6 @@
import { building } from '$app/environment'; import type { Handle } from '@sveltejs/kit/hooks';
import { auth } from '$lib/server/auth'; import { building } from '$app/env';
import type { Handle } from '@sveltejs/kit'; import { auth } from '#lib/server/auth.js';
export const handle: Handle = async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {
// Match the endpoint path here: the dev proxy may terminate HTTPS before Vite. // Match the endpoint path here: the dev proxy may terminate HTTPS before Vite.

View File

@@ -101,7 +101,7 @@
</dl> </dl>
<a <a
href={resolve(`/projects/animaldex/register?animalId=${animal.id}`)} href={resolve(`projects/animaldex/register?animalId=${animal.id}`)}
class="mt-6 inline-flex items-center gap-2 rounded bg-[var(--site-text)] px-4 py-2.5 text-base font-medium text-[var(--site-bg)]" class="mt-6 inline-flex items-center gap-2 rounded bg-[var(--site-text)] px-4 py-2.5 text-base font-medium text-[var(--site-bg)]"
> >
i saw this animal too <ArrowRight size={16} aria-hidden="true" /> i saw this animal too <ArrowRight size={16} aria-hidden="true" />

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { pokemonImage } from '$lib/game-tracker/game-art'; import { pokemonImage } from '#lib/game-tracker/game-art.js';
import { Input } from '$lib/components/ui/input/index.js'; import { Input } from '#lib/components/ui/input/index.js';
import { NativeSelect } from '$lib/components/ui/native-select/index.js'; import { NativeSelect } from '#lib/components/ui/native-select/index.js';
import * as Table from '$lib/components/ui/table/index.js'; import * as Table from '#lib/components/ui/table/index.js';
let moveSearch = $state(''); let moveSearch = $state('');
let moveMethod = $state('all'); let moveMethod = $state('all');
@@ -12,8 +12,8 @@
availabilityLabels, availabilityLabels,
availabilityValues, availabilityValues,
obtainableAvailability obtainableAvailability
} from '$lib/availability'; } from '#lib/availability.js';
import type { PokemonDetail } from '$lib/server/pokemon-detail'; import type { PokemonDetail } from '#lib/server/pokemon-detail.js';
let { let {
number, number,

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { availabilityValues } from '$lib/availability'; import { availabilityValues } from '#lib/availability.js';
import { summarizeProgress } from './progress'; import { summarizeProgress } from './progress';
const catchable = availabilityValues.indexOf('catchable'); const catchable = availabilityValues.indexOf('catchable');

View File

@@ -1,4 +1,4 @@
import { availabilityValues, obtainableAvailability } from '$lib/availability'; import { availabilityValues, obtainableAvailability } from '#lib/availability.js';
type PokemonWithEntries = { type PokemonWithEntries = {
id: number; id: number;

View File

@@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { authClient } from '$lib/auth-client'; import { authClient } from '#lib/auth-client.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '#lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js'; import * as Card from '#lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js'; import { Input } from '#lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js'; import { Label } from '#lib/components/ui/label/index.js';
import { Separator } from '$lib/components/ui/separator/index.js'; import { Separator } from '#lib/components/ui/separator/index.js';
let { let {
mode, mode,
providers, providers,
@@ -43,8 +43,10 @@
return; return;
} }
// Destination is a validated local path, prefixed by resolve('/') above. // Destination is a validated local path, prefixed by resolve('/') above.
// eslint-disable-next-line svelte/no-navigation-without-resolve // goto rejects paths that match no route, so fall back to a full page load.
await goto(destination, { invalidateAll: true }); await goto(destination, { refreshAll: true }).catch(() => {
window.location.href = destination;
});
} catch { } catch {
message = 'Unable to connect. Please try again.'; message = 'Unable to connect. Please try again.';
} finally { } finally {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Component } from 'svelte'; import type { Component } from 'svelte';
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '#lib/components/ui/button/index.js';
let { let {
icon: Icon, icon: Icon,

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Progress } from '$lib/components/ui/progress/index.js'; import { Progress } from '#lib/components/ui/progress/index.js';
let { let {
value, value,

View File

@@ -12,6 +12,7 @@
import ChartNoAxesCombined from 'lucide-svelte/icons/chart-no-axes-combined'; import ChartNoAxesCombined from 'lucide-svelte/icons/chart-no-axes-combined';
import FolderKanban from 'lucide-svelte/icons/folder-kanban'; import FolderKanban from 'lucide-svelte/icons/folder-kanban';
import Gamepad2 from 'lucide-svelte/icons/gamepad-2'; import Gamepad2 from 'lucide-svelte/icons/gamepad-2';
import House from 'lucide-svelte/icons/house';
import Globe2 from 'lucide-svelte/icons/globe-2'; import Globe2 from 'lucide-svelte/icons/globe-2';
import Languages from 'lucide-svelte/icons/languages'; import Languages from 'lucide-svelte/icons/languages';
import Music2 from 'lucide-svelte/icons/music-2'; import Music2 from 'lucide-svelte/icons/music-2';
@@ -30,7 +31,8 @@
'world opinions': Globe2, 'world opinions': Globe2,
'language learner': Languages, 'language learner': Languages,
'money tracker': Wallet, 'money tracker': Wallet,
'cs prices': CandlestickChart 'cs prices': CandlestickChart,
'house tracker': House
}; };
const Icon = $derived(icons[name as keyof typeof icons] ?? FolderKanban); const Icon = $derived(icons[name as keyof typeof icons] ?? FolderKanban);

View File

@@ -4,8 +4,8 @@
buttonVariants, buttonVariants,
type ButtonVariant, type ButtonVariant,
type ButtonSize type ButtonSize
} from '$lib/components/ui/button/index.js'; } from '#lib/components/ui/button/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -4,8 +4,8 @@
buttonVariants, buttonVariants,
type ButtonVariant, type ButtonVariant,
type ButtonSize type ButtonSize
} from '$lib/components/ui/button/index.js'; } from '#lib/components/ui/button/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui'; import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn, type WithoutChild, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChild, type WithoutChildrenOrChild } from '#lib/utils.js';
import AlertDialogOverlay from './alert-dialog-overlay.svelte'; import AlertDialogOverlay from './alert-dialog-overlay.svelte';
import AlertDialogPortal from './alert-dialog-portal.svelte'; import AlertDialogPortal from './alert-dialog-portal.svelte';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui'; import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui'; import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui'; import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -19,7 +19,7 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -23,7 +23,7 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAnchorAttributes } from 'svelte/elements'; import type { HTMLAnchorAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts" module> <script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants'; import { type VariantProps, tv } from 'tailwind-variants';
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
export const buttonVariants = tv({ export const buttonVariants = tv({

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -2,7 +2,7 @@
import { Checkbox as CheckboxPrimitive } from 'bits-ui'; import { Checkbox as CheckboxPrimitive } from 'bits-ui';
import CheckIcon from '@lucide/svelte/icons/check'; import CheckIcon from '@lucide/svelte/icons/check';
import MinusIcon from '@lucide/svelte/icons/minus'; import MinusIcon from '@lucide/svelte/icons/minus';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import * as Dialog from '$lib/components/ui/dialog/index.js'; import * as Dialog from '#lib/components/ui/dialog/index.js';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
import Command from './command.svelte'; import Command from './command.svelte';
import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from 'bits-ui'; import type { Command as CommandPrimitive, Dialog as DialogPrimitive } from 'bits-ui';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui'; import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive, useId } from 'bits-ui'; import { Command as CommandPrimitive, useId } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui'; import { Command as CommandPrimitive } from 'bits-ui';
import * as InputGroup from '$lib/components/ui/input-group/index.js'; import * as InputGroup from '#lib/components/ui/input-group/index.js';
import SearchIcon from '@lucide/svelte/icons/search'; import SearchIcon from '@lucide/svelte/icons/search';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui'; import { Command as CommandPrimitive } from 'bits-ui';
import CheckIcon from '@lucide/svelte/icons/check'; import CheckIcon from '@lucide/svelte/icons/check';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui'; import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui'; import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui'; import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Command as CommandPrimitive } from 'bits-ui'; import { Command as CommandPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
export type CommandRootApi = CommandPrimitive.Root; export type CommandRootApi = CommandPrimitive.Root;

View File

@@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui'; import { Dialog as DialogPrimitive } from 'bits-ui';
import XIcon from '@lucide/svelte/icons/x'; import XIcon from '@lucide/svelte/icons/x';
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '#lib/components/ui/button/index.js';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
import * as Dialog from './index.js'; import * as Dialog from './index.js';
import DialogPortal from './dialog-portal.svelte'; import DialogPortal from './dialog-portal.svelte';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui'; import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui'; import { Dialog as DialogPrimitive } from 'bits-ui';
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '#lib/components/ui/button/index.js';
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui'; import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui'; import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -2,7 +2,7 @@
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import MinusIcon from '@lucide/svelte/icons/minus'; import MinusIcon from '@lucide/svelte/icons/minus';
import CheckIcon from '@lucide/svelte/icons/check'; import CheckIcon from '@lucide/svelte/icons/check';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
import DropdownMenuPortal from './dropdown-menu-portal.svelte'; import DropdownMenuPortal from './dropdown-menu-portal.svelte';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import CheckIcon from '@lucide/svelte/icons/check'; import CheckIcon from '@lucide/svelte/icons/check';
import { cn, type WithoutChild } from '$lib/utils.js'; import { cn, type WithoutChild } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui'; import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right'; import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -21,7 +21,7 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -20,8 +20,8 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '#lib/components/ui/button/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Input } from '$lib/components/ui/input/index.js'; import { Input } from '#lib/components/ui/input/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Textarea } from '$lib/components/ui/textarea/index.js'; import { Textarea } from '#lib/components/ui/textarea/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements'; import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>; type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Label as LabelPrimitive } from 'bits-ui'; import { Label as LabelPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { WithElementRef } from '$lib/utils.js'; import type { WithElementRef } from '#lib/utils.js';
import type { HTMLOptgroupAttributes } from 'svelte/elements'; import type { HTMLOptgroupAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLOptionAttributes } from 'svelte/elements'; import type { HTMLOptionAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'; import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLSelectAttributes } from 'svelte/elements'; import type { HTMLSelectAttributes } from 'svelte/elements';
type NativeSelectProps = Omit<WithElementRef<HTMLSelectAttributes>, 'size'> & { type NativeSelectProps = Omit<WithElementRef<HTMLSelectAttributes>, 'size'> & {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Popover as PopoverPrimitive } from 'bits-ui'; import { Popover as PopoverPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
import PopoverPortal from './popover-portal.svelte'; import PopoverPortal from './popover-portal.svelte';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Popover as PopoverPrimitive } from 'bits-ui'; import { Popover as PopoverPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Progress as ProgressPrimitive } from 'bits-ui'; import { Progress as ProgressPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui'; import { Select as SelectPrimitive } from 'bits-ui';
import { cn, type WithoutChild } from '$lib/utils.js'; import { cn, type WithoutChild } from '#lib/utils.js';
import type { WithoutChildrenOrChild } from '$lib/utils.js'; import type { WithoutChildrenOrChild } from '#lib/utils.js';
import SelectPortal from './select-portal.svelte'; import SelectPortal from './select-portal.svelte';
import SelectScrollDownButton from './select-scroll-down-button.svelte'; import SelectScrollDownButton from './select-scroll-down-button.svelte';
import SelectScrollUpButton from './select-scroll-up-button.svelte'; import SelectScrollUpButton from './select-scroll-up-button.svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui'; import { Select as SelectPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
import type { ComponentProps } from 'svelte'; import type { ComponentProps } from 'svelte';
let { let {

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui'; import { Select as SelectPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui'; import { Select as SelectPrimitive } from 'bits-ui';
import CheckIcon from '@lucide/svelte/icons/check'; import CheckIcon from '@lucide/svelte/icons/check';
import { cn, type WithoutChild } from '$lib/utils.js'; import { cn, type WithoutChild } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js'; import { cn, type WithElementRef } from '#lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements'; import type { HTMLAttributes } from 'svelte/elements';
let { let {

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui'; import { Select as SelectPrimitive } from 'bits-ui';
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'; import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui'; import { Select as SelectPrimitive } from 'bits-ui';
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up'; import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js'; import { cn, type WithoutChildrenOrChild } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Separator } from '$lib/components/ui/separator/index.js'; import { Separator } from '#lib/components/ui/separator/index.js';
import { cn } from '$lib/utils.js'; import { cn } from '#lib/utils.js';
import type { Separator as SeparatorPrimitive } from 'bits-ui'; import type { Separator as SeparatorPrimitive } from 'bits-ui';
let { let {

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Select as SelectPrimitive } from 'bits-ui'; import { Select as SelectPrimitive } from 'bits-ui';
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'; import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
import { cn, type WithoutChild } from '$lib/utils.js'; import { cn, type WithoutChild } from '#lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),

Some files were not shown because too many files have changed in this diff Show More