diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b4bee93..4ab5dc5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,7 +2,7 @@ name: CI
on:
push:
- branches: [main]
+ branches: [main, warehouse-dev]
pull_request:
defaults:
diff --git a/.gitignore b/.gitignore
index 9f11b75..46a1b26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
.idea/
+dev-stderr.log
+dev-stdout.log
diff --git a/resolution-frontend/.env.example b/resolution-frontend/.env.example
index 7f7f4cc..ef1645c 100644
--- a/resolution-frontend/.env.example
+++ b/resolution-frontend/.env.example
@@ -12,6 +12,29 @@ AIRTABLE_BASE_ID=your_base_id
AIRTABLE_TABLE_ID=your_table_id
AIRTABLE_YSWS_TABLE_ID=your_ysws_project_submission_table_id
+# Canada Post API (for shipping rate estimates)
+CP_API_USERNAME=your_cp_api_username
+CP_API_PASSWORD=your_cp_api_password
+CP_CUSTOMER_NUMBER=your_cp_customer_number
+CP_CONTRACT_ID= # optional
+CP_ORIGIN_POSTAL_CODE=A1A1A1
+CP_ENVIRONMENT=development # or "production"
+ZONOS_ACCOUNT_KEY= # Zonos Verified Account key for US duty prepayment
+ZONOS_CREDENTIAL_TOKEN= # Zonos API credential token for landed cost calculations
+
+# Chit Chats API (alternative shipping provider)
+CHITCHATS_CLIENT_ID=your_chitchats_client_id
+CHITCHATS_ACCESS_TOKEN=your_chitchats_access_token
+
+# Hack Club CDN (for warehouse image uploads)
+HACK_CLUB_CDN_API_KEY=sk_cdn_your_key_here
+
+# HCB OAuth (for warehouse order billing — auto-refreshes every 30 min)
+HCB_CLIENT_ID=your_hcb_oauth_client_id
+HCB_CLIENT_SECRET=your_hcb_oauth_client_secret
+HCB_ACCESS_TOKEN=your_hcb_access_token
+HCB_REFRESH_TOKEN=your_hcb_refresh_token
+
# Season Configuration
# Only SEASON_STARTS is required - everything else is auto-calculated
SEASON_STARTS="2026-01-01"
diff --git a/resolution-frontend/.gitignore b/resolution-frontend/.gitignore
index fbd6ae0..9cf95f4 100644
--- a/resolution-frontend/.gitignore
+++ b/resolution-frontend/.gitignore
@@ -24,6 +24,10 @@ vite.config.ts.timestamp-*
/docs
+# Coverage
+/coverage
+
# Prisma
*.db
*.db-journal
+
diff --git a/resolution-frontend/Dockerfile b/resolution-frontend/Dockerfile
index 69351ce..b7ea456 100644
--- a/resolution-frontend/Dockerfile
+++ b/resolution-frontend/Dockerfile
@@ -39,11 +39,25 @@ COPY --from=builder /app/tsconfig.json ./tsconfig.json
# Install drizzle-kit locally so we don't rely on npx fetching it at runtime
RUN npm install --no-save drizzle-kit
+# Copy drizzle files needed for push
+COPY --from=builder /app/drizzle ./drizzle
+COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts
+COPY --from=builder /app/src/lib/server/db/schema.ts ./src/lib/server/db/schema.ts
+
+# Install drizzle-kit for runtime schema push (pruned as devDependency)
+RUN npm install drizzle-kit@0.31.8
+
+# Copy entrypoint script
+COPY entrypoint.sh ./entrypoint.sh
+RUN chmod +x ./entrypoint.sh
+
# Expose the port the app runs on (SvelteKit node adapter starts on 3000 by default)
EXPOSE 3000
# Set Node environment to production
ENV NODE_ENV=production
+# Increase body size limit for image uploads (default 512K is too small)
+ENV BODY_SIZE_LIMIT=10M
-# Push DB schema and start the application
-CMD ["sh", "-c", "npx drizzle-kit push && node build"]
+# Start the application with migrations
+CMD ["./entrypoint.sh"]
diff --git a/resolution-frontend/bun.lock b/resolution-frontend/bun.lock
index b3ba8d7..8792dc6 100644
--- a/resolution-frontend/bun.lock
+++ b/resolution-frontend/bun.lock
@@ -5,21 +5,112 @@
"": {
"name": "revolution-frontend",
"dependencies": {
- "dompurify": "^3.3.1",
+ "@lucia-auth/adapter-drizzle": "^1.1.0",
+ "@monaco-editor/loader": "^1.7.0",
+ "@paralleldrive/cuid2": "^3.3.0",
+ "@tsparticles/fireworks": "^3.9.1",
+ "@types/canvas-confetti": "^1.9.0",
+ "@types/uuid": "^10.0.0",
+ "airtable": "^0.12.2",
+ "bcrypt": "^6.0.0",
+ "canvas-confetti": "^1.9.4",
+ "dompurify": "^3.3.3",
+ "dotenv": "^16.4.0",
+ "drizzle-orm": "^0.45.1",
+ "jsonwebtoken": "^9.0.3",
+ "lucia": "^3.2.2",
+ "monaco-editor": "^0.55.1",
+ "oslo": "^1.2.1",
+ "papaparse": "^5.5.3",
+ "pdf-lib": "^1.17.1",
+ "pg": "^8.13.0",
+ "simple-oauth2": "^5.1.0",
+ "svelte-marked": "^0.8.0",
+ "tsparticles": "^3.9.1",
+ "tsparticles-preset-fireworks": "^2.12.0",
+ "uuid": "^13.0.0",
+ "xml2js": "^0.6.2",
+ "zod": "^4.3.5",
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
- "@sveltejs/kit": "^2.49.1",
+ "@sveltejs/adapter-node": "^5.5.1",
+ "@sveltejs/kit": "^2.49.5",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/svelte": "^5.3.1",
+ "@types/bcrypt": "^6.0.0",
"@types/dompurify": "^3.2.0",
- "svelte": "^5.45.6",
+ "@types/jsonwebtoken": "^9.0.10",
+ "@types/papaparse": "^5.5.2",
+ "@types/pg": "^8.16.0",
+ "@types/simple-oauth2": "^5.0.8",
+ "@types/xml2js": "^0.4.14",
+ "@vitest/coverage-v8": "^4.0.18",
+ "drizzle-kit": "^0.31.8",
+ "jsdom": "^28.1.0",
+ "svelte": "^5.46.4",
"svelte-check": "^4.3.4",
"typescript": "^5.9.3",
"vite": "^7.2.6",
+ "vitest": "^4.0.18",
},
},
},
+ "overrides": {
+ "cookie": ">=0.7.0",
+ "dompurify": "^3.3.3",
+ "esbuild": ">=0.25.0",
+ },
"packages": {
+ "@acemir/cssom": ["@acemir/cssom@0.9.31", "", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="],
+
+ "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
+
+ "@asamuzakjp/css-color": ["@asamuzakjp/css-color@5.1.9", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-zd9c/Wdso6v1U7v6w3i/hbAr4K7NaSHImdpvmLt+Y9ea5BhilnIGNkfhOJ7FEIuPipAnE9tZeDOll05WDT0kgg=="],
+
+ "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@6.8.1", "", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.6" } }, "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ=="],
+
+ "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="],
+
+ "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
+
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+
+ "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
+
+ "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
+
+ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
+
+ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
+
+ "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="],
+
+ "@csstools/color-helpers": ["@csstools/color-helpers@6.0.2", "", {}, "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q=="],
+
+ "@csstools/css-calc": ["@csstools/css-calc@3.1.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ=="],
+
+ "@csstools/css-color-parser": ["@csstools/css-color-parser@4.0.2", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.1.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw=="],
+
+ "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="],
+
+ "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.2", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA=="],
+
+ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="],
+
+ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
+
+ "@emnapi/core": ["@emnapi/core@0.45.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw=="],
+
+ "@emnapi/runtime": ["@emnapi/runtime@0.45.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w=="],
+
+ "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
+
+ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
@@ -72,6 +163,18 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
+ "@exodus/bytes": ["@exodus/bytes@1.15.0", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ=="],
+
+ "@hapi/boom": ["@hapi/boom@10.0.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA=="],
+
+ "@hapi/bourne": ["@hapi/bourne@3.0.0", "", {}, "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="],
+
+ "@hapi/hoek": ["@hapi/hoek@11.0.7", "", {}, "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ=="],
+
+ "@hapi/topo": ["@hapi/topo@5.1.0", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg=="],
+
+ "@hapi/wreck": ["@hapi/wreck@18.1.0", "", { "dependencies": { "@hapi/boom": "^10.0.1", "@hapi/bourne": "^3.0.0", "@hapi/hoek": "^11.0.2" } }, "sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w=="],
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -82,51 +185,151 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+ "@lucia-auth/adapter-drizzle": ["@lucia-auth/adapter-drizzle@1.1.0", "", { "peerDependencies": { "drizzle-orm": ">= 0.29 <1", "lucia": "3.x" } }, "sha512-iCTnZWvfI5lLZOdUHZYiXA1jaspIFEeo2extLxQ3DjP3uOVys7IPwBi7zezLIRu9dhro4H4Kji+7gSYyjcef2A=="],
+
+ "@monaco-editor/loader": ["@monaco-editor/loader@1.7.0", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="],
+
+ "@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
+
+ "@node-rs/argon2": ["@node-rs/argon2@1.7.0", "", { "optionalDependencies": { "@node-rs/argon2-android-arm-eabi": "1.7.0", "@node-rs/argon2-android-arm64": "1.7.0", "@node-rs/argon2-darwin-arm64": "1.7.0", "@node-rs/argon2-darwin-x64": "1.7.0", "@node-rs/argon2-freebsd-x64": "1.7.0", "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", "@node-rs/argon2-linux-arm64-gnu": "1.7.0", "@node-rs/argon2-linux-arm64-musl": "1.7.0", "@node-rs/argon2-linux-x64-gnu": "1.7.0", "@node-rs/argon2-linux-x64-musl": "1.7.0", "@node-rs/argon2-wasm32-wasi": "1.7.0", "@node-rs/argon2-win32-arm64-msvc": "1.7.0", "@node-rs/argon2-win32-ia32-msvc": "1.7.0", "@node-rs/argon2-win32-x64-msvc": "1.7.0" } }, "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog=="],
+
+ "@node-rs/argon2-android-arm-eabi": ["@node-rs/argon2-android-arm-eabi@1.7.0", "", { "os": "android", "cpu": "arm" }, "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg=="],
+
+ "@node-rs/argon2-android-arm64": ["@node-rs/argon2-android-arm64@1.7.0", "", { "os": "android", "cpu": "arm64" }, "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A=="],
+
+ "@node-rs/argon2-darwin-arm64": ["@node-rs/argon2-darwin-arm64@1.7.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw=="],
+
+ "@node-rs/argon2-darwin-x64": ["@node-rs/argon2-darwin-x64@1.7.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw=="],
+
+ "@node-rs/argon2-freebsd-x64": ["@node-rs/argon2-freebsd-x64@1.7.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA=="],
+
+ "@node-rs/argon2-linux-arm-gnueabihf": ["@node-rs/argon2-linux-arm-gnueabihf@1.7.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg=="],
+
+ "@node-rs/argon2-linux-arm64-gnu": ["@node-rs/argon2-linux-arm64-gnu@1.7.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g=="],
+
+ "@node-rs/argon2-linux-arm64-musl": ["@node-rs/argon2-linux-arm64-musl@1.7.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A=="],
+
+ "@node-rs/argon2-linux-x64-gnu": ["@node-rs/argon2-linux-x64-gnu@1.7.0", "", { "os": "linux", "cpu": "x64" }, "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ=="],
+
+ "@node-rs/argon2-linux-x64-musl": ["@node-rs/argon2-linux-x64-musl@1.7.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA=="],
+
+ "@node-rs/argon2-wasm32-wasi": ["@node-rs/argon2-wasm32-wasi@1.7.0", "", { "dependencies": { "@emnapi/core": "^0.45.0", "@emnapi/runtime": "^0.45.0", "@tybys/wasm-util": "^0.8.1", "memfs-browser": "^3.4.13000" }, "cpu": "none" }, "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w=="],
+
+ "@node-rs/argon2-win32-arm64-msvc": ["@node-rs/argon2-win32-arm64-msvc@1.7.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA=="],
+
+ "@node-rs/argon2-win32-ia32-msvc": ["@node-rs/argon2-win32-ia32-msvc@1.7.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg=="],
+
+ "@node-rs/argon2-win32-x64-msvc": ["@node-rs/argon2-win32-x64-msvc@1.7.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q=="],
+
+ "@node-rs/bcrypt": ["@node-rs/bcrypt@1.9.0", "", { "optionalDependencies": { "@node-rs/bcrypt-android-arm-eabi": "1.9.0", "@node-rs/bcrypt-android-arm64": "1.9.0", "@node-rs/bcrypt-darwin-arm64": "1.9.0", "@node-rs/bcrypt-darwin-x64": "1.9.0", "@node-rs/bcrypt-freebsd-x64": "1.9.0", "@node-rs/bcrypt-linux-arm-gnueabihf": "1.9.0", "@node-rs/bcrypt-linux-arm64-gnu": "1.9.0", "@node-rs/bcrypt-linux-arm64-musl": "1.9.0", "@node-rs/bcrypt-linux-x64-gnu": "1.9.0", "@node-rs/bcrypt-linux-x64-musl": "1.9.0", "@node-rs/bcrypt-wasm32-wasi": "1.9.0", "@node-rs/bcrypt-win32-arm64-msvc": "1.9.0", "@node-rs/bcrypt-win32-ia32-msvc": "1.9.0", "@node-rs/bcrypt-win32-x64-msvc": "1.9.0" } }, "sha512-u2OlIxW264bFUfvbFqDz9HZKFjwe8FHFtn7T/U8mYjPZ7DWYpbUB+/dkW/QgYfMSfR0ejkyuWaBBe0coW7/7ig=="],
+
+ "@node-rs/bcrypt-android-arm-eabi": ["@node-rs/bcrypt-android-arm-eabi@1.9.0", "", { "os": "android", "cpu": "arm" }, "sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA=="],
+
+ "@node-rs/bcrypt-android-arm64": ["@node-rs/bcrypt-android-arm64@1.9.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A=="],
+
+ "@node-rs/bcrypt-darwin-arm64": ["@node-rs/bcrypt-darwin-arm64@1.9.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CQiS+F9Pa0XozvkXR1g7uXE9QvBOPOplDg0iCCPRYTN9PqA5qYxhwe48G3o+v2UeQceNRrbnEtWuANm7JRqIhw=="],
+
+ "@node-rs/bcrypt-darwin-x64": ["@node-rs/bcrypt-darwin-x64@1.9.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug=="],
+
+ "@node-rs/bcrypt-freebsd-x64": ["@node-rs/bcrypt-freebsd-x64@1.9.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg=="],
+
+ "@node-rs/bcrypt-linux-arm-gnueabihf": ["@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0", "", { "os": "linux", "cpu": "arm" }, "sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA=="],
+
+ "@node-rs/bcrypt-linux-arm64-gnu": ["@node-rs/bcrypt-linux-arm64-gnu@1.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q=="],
+
+ "@node-rs/bcrypt-linux-arm64-musl": ["@node-rs/bcrypt-linux-arm64-musl@1.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew=="],
+
+ "@node-rs/bcrypt-linux-x64-gnu": ["@node-rs/bcrypt-linux-x64-gnu@1.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ=="],
+
+ "@node-rs/bcrypt-linux-x64-musl": ["@node-rs/bcrypt-linux-x64-musl@1.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg=="],
+
+ "@node-rs/bcrypt-wasm32-wasi": ["@node-rs/bcrypt-wasm32-wasi@1.9.0", "", { "dependencies": { "@emnapi/core": "^0.45.0", "@emnapi/runtime": "^0.45.0", "@tybys/wasm-util": "^0.8.1", "memfs-browser": "^3.4.13000" }, "cpu": "none" }, "sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw=="],
+
+ "@node-rs/bcrypt-win32-arm64-msvc": ["@node-rs/bcrypt-win32-arm64-msvc@1.9.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw=="],
+
+ "@node-rs/bcrypt-win32-ia32-msvc": ["@node-rs/bcrypt-win32-ia32-msvc@1.9.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA=="],
+
+ "@node-rs/bcrypt-win32-x64-msvc": ["@node-rs/bcrypt-win32-x64-msvc@1.9.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w=="],
+
+ "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
+
+ "@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="],
+
+ "@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="],
+
+ "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
+
+ "@paralleldrive/cuid2": ["@paralleldrive/cuid2@3.3.0", "", { "dependencies": { "@noble/hashes": "^2.0.1", "bignumber.js": "^9.3.1", "error-causes": "^3.0.2" }, "bin": { "cuid2": "bin/cuid2.js" } }, "sha512-OqiFvSOF0dBSesELYY2CAMa4YINvlLpvKOz/rv6NeZEqiyttlHgv98Juwv4Ch+GrEV7IZ8jfI2VcEoYUjXXCjw=="],
+
+ "@pdf-lib/standard-fonts": ["@pdf-lib/standard-fonts@1.0.0", "", { "dependencies": { "pako": "^1.0.6" } }, "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA=="],
+
+ "@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="],
+
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="],
+ "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.2", "", { "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-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg=="],
+
+ "@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/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
+
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
+
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
+
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
+
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
+
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
+
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
+
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="],
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="],
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="],
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="],
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="],
+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="],
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="],
+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="],
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="],
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
- "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="],
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="],
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="],
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="],
+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="],
+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="],
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="],
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
- "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="],
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="],
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="],
+ "@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="],
- "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="],
+ "@sideway/formula": ["@sideway/formula@3.0.1", "", {}, "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="],
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="],
+ "@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
@@ -134,58 +337,370 @@
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@7.0.0", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw=="],
- "@sveltejs/kit": ["@sveltejs/kit@2.49.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ=="],
+ "@sveltejs/adapter-node": ["@sveltejs/adapter-node@5.5.4", "", { "dependencies": { "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "rollup": "^4.59.0" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0" } }, "sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ=="],
+
+ "@sveltejs/kit": ["@sveltejs/kit@2.57.1", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.4", "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-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.1", "", { "dependencies": { "debug": "^4.4.1" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA=="],
+ "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
+
+ "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="],
+
+ "@testing-library/svelte": ["@testing-library/svelte@5.3.1", "", { "dependencies": { "@testing-library/dom": "9.x.x || 10.x.x", "@testing-library/svelte-core": "1.0.0" }, "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", "vite": "*", "vitest": "*" }, "optionalPeers": ["vite", "vitest"] }, "sha512-8Ez7ZOqW5geRf9PF5rkuopODe5RGy3I9XR+kc7zHh26gBiktLaxTfKmhlGaSHYUOTQE7wFsLMN9xCJVCszw47w=="],
+
+ "@testing-library/svelte-core": ["@testing-library/svelte-core@1.0.0", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" } }, "sha512-VkUePoLV6oOYwSUvX6ShA8KLnJqZiYMIbP2JW2t0GLWLkJxKGvuH5qrrZBV/X7cXFnLGuFQEC7RheYiZOW68KQ=="],
+
+ "@tsparticles/basic": ["@tsparticles/basic@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1", "@tsparticles/move-base": "3.9.1", "@tsparticles/plugin-hex-color": "3.9.1", "@tsparticles/plugin-hsl-color": "3.9.1", "@tsparticles/plugin-rgb-color": "3.9.1", "@tsparticles/shape-circle": "3.9.1", "@tsparticles/updater-color": "3.9.1", "@tsparticles/updater-opacity": "3.9.1", "@tsparticles/updater-out-modes": "3.9.1", "@tsparticles/updater-size": "3.9.1" } }, "sha512-ijr2dHMx0IQHqhKW3qA8tfwrR2XYbbWYdaJMQuBo2CkwBVIhZ76U+H20Y492j/NXpd1FUnt2aC0l4CEVGVGdeQ=="],
+
+ "@tsparticles/effect-trail": ["@tsparticles/effect-trail@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-6vo7rsI+xta8Cqs9TMW00uoyqddHjAu8qVBKjMicSkVm18A6BxF5Puv0MVgTkikdWa3MMztXOqJI3YmWjpJb0w=="],
+
+ "@tsparticles/engine": ["@tsparticles/engine@3.9.1", "", {}, "sha512-DpdgAhWMZ3Eh2gyxik8FXS6BKZ8vyea+Eu5BC4epsahqTGY9V3JGGJcXC6lRJx6cPMAx1A0FaQAojPF3v6rkmQ=="],
+
+ "@tsparticles/fireworks": ["@tsparticles/fireworks@3.9.1", "", { "dependencies": { "@tsparticles/basic": "3.9.1", "@tsparticles/effect-trail": "3.9.1", "@tsparticles/engine": "3.9.1", "@tsparticles/plugin-emitters": "3.9.1", "@tsparticles/plugin-emitters-shape-square": "3.9.1", "@tsparticles/plugin-sounds": "3.9.1", "@tsparticles/updater-destroy": "3.9.1", "@tsparticles/updater-life": "3.9.1", "@tsparticles/updater-rotate": "3.9.1" } }, "sha512-75zJlQxJEISsRLr4TsQ8kxbZT+kg/uScqA+WVvHHmdA3UdSvBrgdcZPY0QjdmjZxWwab+O1qDRzf9kQfsr4luQ=="],
+
+ "@tsparticles/interaction-external-attract": ["@tsparticles/interaction-external-attract@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-5AJGmhzM9o4AVFV24WH5vSqMBzOXEOzIdGLIr+QJf4fRh9ZK62snsusv/ozKgs2KteRYQx+L7c5V3TqcDy2upg=="],
+
+ "@tsparticles/interaction-external-bounce": ["@tsparticles/interaction-external-bounce@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-bv05+h70UIHOTWeTsTI1AeAmX6R3s8nnY74Ea6p6AbQjERzPYIa0XY19nq/hA7+Nrg+EissP5zgoYYeSphr85A=="],
+
+ "@tsparticles/interaction-external-bubble": ["@tsparticles/interaction-external-bubble@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-tbd8ox/1GPl+zr+KyHQVV1bW88GE7OM6i4zql801YIlCDrl9wgTDdDFGIy9X7/cwTvTrCePhrfvdkUamXIribQ=="],
+
+ "@tsparticles/interaction-external-connect": ["@tsparticles/interaction-external-connect@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-sq8YfUNsIORjXHzzW7/AJQtfi/qDqLnYG2qOSE1WOsog39MD30RzmiOloejOkfNeUdcGUcfsDgpUuL3UhzFUOA=="],
+
+ "@tsparticles/interaction-external-grab": ["@tsparticles/interaction-external-grab@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-QwXza+sMMWDaMiFxd8y2tJwUK6c+nNw554+/9+tEZeTTk2fCbB0IJ7p/TH6ZGWDL0vo2muK54Njv2fEey191ow=="],
+
+ "@tsparticles/interaction-external-pause": ["@tsparticles/interaction-external-pause@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-Gzv4/FeNir0U/tVM9zQCqV1k+IAgaFjDU3T30M1AeAsNGh/rCITV2wnT7TOGFkbcla27m4Yxa+Fuab8+8pzm+g=="],
+
+ "@tsparticles/interaction-external-push": ["@tsparticles/interaction-external-push@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-GvnWF9Qy4YkZdx+WJL2iy9IcgLvzOIu3K7aLYJFsQPaxT8d9TF8WlpoMlWKnJID6H5q4JqQuMRKRyWH8aAKyQw=="],
+
+ "@tsparticles/interaction-external-remove": ["@tsparticles/interaction-external-remove@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-yPThm4UDWejDOWW5Qc8KnnS2EfSo5VFcJUQDWc1+Wcj17xe7vdSoiwwOORM0PmNBzdDpSKQrte/gUnoqaUMwOA=="],
+
+ "@tsparticles/interaction-external-repulse": ["@tsparticles/interaction-external-repulse@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-/LBppXkrMdvLHlEKWC7IykFhzrz+9nebT2fwSSFXK4plEBxDlIwnkDxd3FbVOAbnBvx4+L8+fbrEx+RvC8diAw=="],
+
+ "@tsparticles/interaction-external-slow": ["@tsparticles/interaction-external-slow@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-1ZYIR/udBwA9MdSCfgADsbDXKSFS0FMWuPWz7bm79g3sUxcYkihn+/hDhc6GXvNNR46V1ocJjrj0u6pAynS1KQ=="],
+
+ "@tsparticles/interaction-external-trail": ["@tsparticles/interaction-external-trail@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-Au0v2oiqfKTemI/4bzjD4dUXzIngB5Q2T4nJcMCYpP24uZfwZh5xTjUMH7gyJyyaRTdMl9IJrp8ySjyYbLfeGg=="],
+
+ "@tsparticles/interaction-particles-attract": ["@tsparticles/interaction-particles-attract@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-CYYYowJuGwRLUixQcSU/48PTKM8fCUYThe0hXwQ+yRMLAn053VHzL7NNZzKqEIeEyt5oJoy9KcvubjKWbzMBLQ=="],
+
+ "@tsparticles/interaction-particles-collisions": ["@tsparticles/interaction-particles-collisions@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-ggGyjW/3v1yxvYW1IF1EMT15M6w31y5zfNNUPkqd/IXRNPYvm0Z0ayhp+FKmz70M5p0UxxPIQHTvAv9Jqnuj8w=="],
+
+ "@tsparticles/interaction-particles-links": ["@tsparticles/interaction-particles-links@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-MsLbMjy1vY5M5/hu/oa5OSRZAUz49H3+9EBMTIOThiX+a+vpl3sxc9AqNd9gMsPbM4WJlub8T6VBZdyvzez1Vg=="],
+
+ "@tsparticles/move-base": ["@tsparticles/move-base@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-X4huBS27d8srpxwOxliWPUt+NtCwY+8q/cx1DvQxyqmTA8VFCGpcHNwtqiN+9JicgzOvSuaORVqUgwlsc7h4pQ=="],
+
+ "@tsparticles/move-parallax": ["@tsparticles/move-parallax@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-whlOR0bVeyh6J/hvxf/QM3DqvNnITMiAQ0kro6saqSDItAVqg4pYxBfEsSOKq7EhjxNvfhhqR+pFMhp06zoCVA=="],
+
+ "@tsparticles/plugin-absorbers": ["@tsparticles/plugin-absorbers@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-q9SQllpbPPgw1+euxHPYCFawOVUazQkkwnleiIgpYSiimlCyjIdwGnFPSNe1Sypzqmr2h6oOyX2vkK5ZVNEu8A=="],
+
+ "@tsparticles/plugin-easing-quad": ["@tsparticles/plugin-easing-quad@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-C2UJOca5MTDXKUTBXj30Kiqr5UyID+xrY/LxicVWWZPczQW2bBxbIbfq9ULvzGDwBTxE2rdvIB8YFKmDYO45qw=="],
+
+ "@tsparticles/plugin-emitters": ["@tsparticles/plugin-emitters@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-h7opR8SoFWBmVHceDLJUerLENaPfkJSh2zQYvzmLj2L+V3VLS1QDgty+4QZVeZfqNROmgQw2eLFA5El1E0sqqw=="],
+
+ "@tsparticles/plugin-emitters-shape-circle": ["@tsparticles/plugin-emitters-shape-circle@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1", "@tsparticles/plugin-emitters": "3.9.1" } }, "sha512-z+9MsAPWr++sNz6N6303rRDjusW0BIPhHY51E5eXGDcRdOqrESDs6y99AJ/6Kdb/PpibCIYjFY9jVi2JJADPRA=="],
+
+ "@tsparticles/plugin-emitters-shape-square": ["@tsparticles/plugin-emitters-shape-square@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1", "@tsparticles/plugin-emitters": "3.9.1" } }, "sha512-dhA1c7FKs19B8lgTf25OTA3JoptNA+rjorsqCFuY1BZDI8g9E8DNqikUge14/W7nZN96+98hY+ghxSl4K2YsgA=="],
+
+ "@tsparticles/plugin-hex-color": ["@tsparticles/plugin-hex-color@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-vZgZ12AjUicJvk7AX4K2eAmKEQX/D1VEjEPFhyjbgI7A65eX72M465vVKIgNA6QArLZ1DLs7Z787LOE6GOBWsg=="],
+
+ "@tsparticles/plugin-hsl-color": ["@tsparticles/plugin-hsl-color@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-jJd1iGgRwX6eeNjc1zUXiJivaqC5UE+SC2A3/NtHwwoQrkfxGWmRHOsVyLnOBRcCPgBp/FpdDe6DIDjCMO715w=="],
+
+ "@tsparticles/plugin-rgb-color": ["@tsparticles/plugin-rgb-color@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-SBxk7f1KBfXeTnnklbE2Hx4jBgh6I6HOtxb+Os1gTp0oaghZOkWcCD2dP4QbUu7fVNCMOcApPoMNC8RTFcy9wQ=="],
+
+ "@tsparticles/plugin-sounds": ["@tsparticles/plugin-sounds@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-Hw2hmKmkkuFnNhOBwkvREXLULbAUwXXMeenyIy0jUGGuvjjI7rivJeFU6djCaaI0q4SbIlBFuu9mLZw2FEfwEA=="],
+
+ "@tsparticles/shape-circle": ["@tsparticles/shape-circle@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-DqZFLjbuhVn99WJ+A9ajz9YON72RtCcvubzq6qfjFmtwAK7frvQeb6iDTp6Ze9FUipluxVZWVRG4vWTxi2B+/g=="],
+
+ "@tsparticles/shape-emoji": ["@tsparticles/shape-emoji@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-ifvY63usuT+hipgVHb8gelBHSeF6ryPnMxAAEC1RGHhhXfpSRWMtE6ybr+pSsYU52M3G9+TF84v91pSwNrb9ZQ=="],
+
+ "@tsparticles/shape-image": ["@tsparticles/shape-image@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-fCA5eme8VF3oX8yNVUA0l2SLDKuiZObkijb0z3Ky0qj1HUEVlAuEMhhNDNB9E2iELTrWEix9z7BFMePp2CC7AA=="],
+
+ "@tsparticles/shape-line": ["@tsparticles/shape-line@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-wT8NSp0N9HURyV05f371cHKcNTNqr0/cwUu6WhBzbshkYGy1KZUP9CpRIh5FCrBpTev34mEQfOXDycgfG0KiLQ=="],
+
+ "@tsparticles/shape-polygon": ["@tsparticles/shape-polygon@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-dA77PgZdoLwxnliH6XQM/zF0r4jhT01pw5y7XTeTqws++hg4rTLV9255k6R6eUqKq0FPSW1/WBsBIl7q/MmrqQ=="],
+
+ "@tsparticles/shape-square": ["@tsparticles/shape-square@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-DKGkDnRyZrAm7T2ipqNezJahSWs6xd9O5LQLe5vjrYm1qGwrFxJiQaAdlb00UNrexz1/SA7bEoIg4XKaFa7qhQ=="],
+
+ "@tsparticles/shape-star": ["@tsparticles/shape-star@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-kdMJpi8cdeb6vGrZVSxTG0JIjCwIenggqk0EYeKAwtOGZFBgL7eHhF2F6uu1oq8cJAbXPujEoabnLsz6mW8XaA=="],
+
+ "@tsparticles/shape-text": ["@tsparticles/shape-text@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-oNsLHI0lGkIXoUw3W598iwd7dtoHCDrwpwJRGnQzgfk6T5a9dCpSD5vDeQN89lr3BUbVui4lhxq+/TyC64oAqA=="],
+
+ "@tsparticles/slim": ["@tsparticles/slim@3.9.1", "", { "dependencies": { "@tsparticles/basic": "3.9.1", "@tsparticles/engine": "3.9.1", "@tsparticles/interaction-external-attract": "3.9.1", "@tsparticles/interaction-external-bounce": "3.9.1", "@tsparticles/interaction-external-bubble": "3.9.1", "@tsparticles/interaction-external-connect": "3.9.1", "@tsparticles/interaction-external-grab": "3.9.1", "@tsparticles/interaction-external-pause": "3.9.1", "@tsparticles/interaction-external-push": "3.9.1", "@tsparticles/interaction-external-remove": "3.9.1", "@tsparticles/interaction-external-repulse": "3.9.1", "@tsparticles/interaction-external-slow": "3.9.1", "@tsparticles/interaction-particles-attract": "3.9.1", "@tsparticles/interaction-particles-collisions": "3.9.1", "@tsparticles/interaction-particles-links": "3.9.1", "@tsparticles/move-parallax": "3.9.1", "@tsparticles/plugin-easing-quad": "3.9.1", "@tsparticles/shape-emoji": "3.9.1", "@tsparticles/shape-image": "3.9.1", "@tsparticles/shape-line": "3.9.1", "@tsparticles/shape-polygon": "3.9.1", "@tsparticles/shape-square": "3.9.1", "@tsparticles/shape-star": "3.9.1", "@tsparticles/updater-life": "3.9.1", "@tsparticles/updater-rotate": "3.9.1", "@tsparticles/updater-stroke-color": "3.9.1" } }, "sha512-CL5cDmADU7sDjRli0So+hY61VMbdroqbArmR9Av+c1Fisa5ytr6QD7Jv62iwU2S6rvgicEe9OyRmSy5GIefwZw=="],
+
+ "@tsparticles/updater-color": ["@tsparticles/updater-color@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-XGWdscrgEMA8L5E7exsE0f8/2zHKIqnTrZymcyuFBw2DCB6BIV+5z6qaNStpxrhq3DbIxxhqqcybqeOo7+Alpg=="],
+
+ "@tsparticles/updater-destroy": ["@tsparticles/updater-destroy@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-MjMzEhZwCQIbxO6ZRM0eXsHVwmlXuUqwC43WCPZCpjhK3AJrMu3KR4xsJieFTWIbVNguAvbgoTB10FfJOUU5VA=="],
+
+ "@tsparticles/updater-life": ["@tsparticles/updater-life@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-Oi8aF2RIwMMsjssUkCB6t3PRpENHjdZf6cX92WNfAuqXtQphr3OMAkYFJFWkvyPFK22AVy3p/cFt6KE5zXxwAA=="],
+
+ "@tsparticles/updater-opacity": ["@tsparticles/updater-opacity@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-w778LQuRZJ+IoWzeRdrGykPYSSaTeWfBvLZ2XwYEkh/Ss961InOxZKIpcS6i5Kp/Zfw0fS1ZAuqeHwuj///Osw=="],
+
+ "@tsparticles/updater-out-modes": ["@tsparticles/updater-out-modes@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-cKQEkAwbru+hhKF+GTsfbOvuBbx2DSB25CxOdhtW2wRvDBoCnngNdLw91rs+0Cex4tgEeibkebrIKFDDE6kELg=="],
+
+ "@tsparticles/updater-roll": ["@tsparticles/updater-roll@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-zl4JeM3gUBJ0uttmIsond3lrZ3f3AkItFeS0Lhj/7jiCKfUoRyyOMrcBk8R1AhW7lI+7ko1iBs3jhO0jnxz9vg=="],
+
+ "@tsparticles/updater-rotate": ["@tsparticles/updater-rotate@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-9BfKaGfp28JN82MF2qs6Ae/lJr9EColMfMTHqSKljblwbpVDHte4umuwKl3VjbRt87WD9MGtla66NTUYl+WxuQ=="],
+
+ "@tsparticles/updater-size": ["@tsparticles/updater-size@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-3NSVs0O2ApNKZXfd+y/zNhTXSFeG1Pw4peI8e6z/q5+XLbmue9oiEwoPy/tQLaark3oNj3JU7Q903ZijPyXSzw=="],
+
+ "@tsparticles/updater-stroke-color": ["@tsparticles/updater-stroke-color@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-3x14+C2is9pZYTg9T2TiA/aM1YMq4wLdYaZDcHm3qO30DZu5oeQq0rm/6w+QOGKYY1Z3Htg9rlSUZkhTHn7eDA=="],
+
+ "@tsparticles/updater-tilt": ["@tsparticles/updater-tilt@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-PB2yaoyXRmSk4iIVgjtRrzOxXMK9mjeAQHIJGtT4faq46Z8cbIIEFgjTwqrUV8qOrNg/h4sm5NE/s0qsTYjp1Q=="],
+
+ "@tsparticles/updater-twinkle": ["@tsparticles/updater-twinkle@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-xgTcYr6LmP44IPIBeQmEExN2Y5Nfl3ikmC08eOh5nZy/ta6ORP+JTsprrnfuv/O2DwTyoqFLkZ16hZfkdc1yOQ=="],
+
+ "@tsparticles/updater-wobble": ["@tsparticles/updater-wobble@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1" } }, "sha512-c99Ogy9q4QWO+zsDXol0UnpUwZiY2UucFb8ltuDv9AlbGUeprygoub8jhgT5pEDv+GdzWOJGSgq7rfgv9cHBrg=="],
+
+ "@tybys/wasm-util": ["@tybys/wasm-util@0.8.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q=="],
+
+ "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
+
+ "@types/bcrypt": ["@types/bcrypt@6.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ=="],
+
+ "@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="],
+
+ "@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/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
+
"@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+ "@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
+
+ "@types/marked": ["@types/marked@6.0.0", "", { "dependencies": { "marked": "*" } }, "sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA=="],
+
+ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+
+ "@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="],
+
+ "@types/papaparse": ["@types/papaparse@5.5.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA=="],
+
+ "@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
+
+ "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
+
+ "@types/simple-oauth2": ["@types/simple-oauth2@5.0.8", "", {}, "sha512-TehQqoOGdy3/rmFsCEGgnt1f4JhUCA0joWemGGCTbVYvoZvfBjkRsBFYmz8k0V/sn2XQZHe33L4lWxqMhIO3tQ=="],
+
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
+ "@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="],
+
+ "@types/xml2js": ["@types/xml2js@0.4.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ=="],
+
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="],
+
+ "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.4", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.4", "vitest": "4.1.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w=="],
+
+ "@vitest/expect": ["@vitest/expect@4.1.4", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww=="],
+
+ "@vitest/mocker": ["@vitest/mocker@4.1.4", "", { "dependencies": { "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg=="],
+
+ "@vitest/pretty-format": ["@vitest/pretty-format@4.1.4", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A=="],
+
+ "@vitest/runner": ["@vitest/runner@4.1.4", "", { "dependencies": { "@vitest/utils": "4.1.4", "pathe": "^2.0.3" } }, "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ=="],
+
+ "@vitest/snapshot": ["@vitest/snapshot@4.1.4", "", { "dependencies": { "@vitest/pretty-format": "4.1.4", "@vitest/utils": "4.1.4", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw=="],
+
+ "@vitest/spy": ["@vitest/spy@4.1.4", "", {}, "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ=="],
+
+ "@vitest/utils": ["@vitest/utils@4.1.4", "", { "dependencies": { "@vitest/pretty-format": "4.1.4", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw=="],
+
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "abortcontroller-polyfill": ["abortcontroller-polyfill@1.7.8", "", {}, "sha512-9f1iZ2uWh92VcrU9Y8x+LdM4DLj75VE0MJB8zuF1iUnroEptStw+DQ8EQPMUdfe5k+PkB1uUfDQfWbhstH8LrQ=="],
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
+ "airtable": ["airtable@0.12.2", "", { "dependencies": { "@types/node": ">=8.0.0 <15", "abort-controller": "^3.0.0", "abortcontroller-polyfill": "^1.4.0", "lodash": "^4.17.21", "node-fetch": "^2.6.7" } }, "sha512-HS3VytUBTKj8A0vPl7DDr5p/w3IOGv6RXL0fv7eczOWAtj9Xe8ri4TAiZRXoOyo+Z/COADCj+oARFenbxhmkIg=="],
+
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
+
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
+ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+
+ "ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg=="],
+
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
+ "bcrypt": ["bcrypt@6.0.0", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg=="],
+
+ "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
+
+ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
+
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
+
+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
+ "canvas-confetti": ["canvas-confetti@1.9.4", "", {}, "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw=="],
+
+ "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
+
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
- "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
+ "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
+
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
+ "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
+
+ "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
+
+ "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
+
+ "cssstyle": ["cssstyle@6.2.0", "", { "dependencies": { "@asamuzakjp/css-color": "^5.0.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.28", "css-tree": "^3.1.0", "lru-cache": "^11.2.6" } }, "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig=="],
+
+ "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
+
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
- "devalue": ["devalue@5.6.1", "", {}, "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A=="],
+ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
- "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="],
+ "devalue": ["devalue@5.7.1", "", {}, "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA=="],
+
+ "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
+
+ "dompurify": ["dompurify@3.3.3", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="],
+
+ "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+
+ "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
+
+ "drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="],
+
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
+
+ "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+
+ "error-causes": ["error-causes@3.0.2", "", {}, "sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw=="],
+
+ "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
- "esrap": ["esrap@2.2.1", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg=="],
+ "esrap": ["esrap@2.2.4", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@typescript-eslint/types": "^8.2.0" } }, "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg=="],
+
+ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
+
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
+ "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+ "fs-monkey": ["fs-monkey@1.1.0", "", {}, "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw=="],
+
"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.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="],
+
+ "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
+
+ "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
+
+ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
+
+ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
+
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
+
+ "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
+
+ "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="],
+
+ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
+
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
+ "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
+
+ "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
+
+ "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
+
+ "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="],
+
+ "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="],
+
+ "jsdom": ["jsdom@28.1.0", "", { "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.8.1", "@bramus/specificity": "^2.4.2", "@exodus/bytes": "^1.11.0", "cssstyle": "^6.0.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug=="],
+
+ "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
+
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
+
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
+
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
+ "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
+
+ "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
+
+ "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
+
+ "lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
+
+ "lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
+
+ "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
+
+ "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
+
+ "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
+
+ "lru-cache": ["lru-cache@11.3.3", "", {}, "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ=="],
+
+ "lucia": ["lucia@3.2.2", "", { "dependencies": { "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0" } }, "sha512-P1FlFBGCMPMXu+EGdVD9W4Mjm0DqsusmKgO7Xc33mI5X1bklmsQb0hfzPhXomQr9waWIBDsiOjvr1e6BTaUqpA=="],
+
+ "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+ "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="],
+
+ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
+
+ "marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="],
+
+ "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
+
+ "memfs": ["memfs@3.5.3", "", { "dependencies": { "fs-monkey": "^1.0.4" } }, "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw=="],
+
+ "memfs-browser": ["memfs-browser@3.5.10302", "", { "dependencies": { "memfs": "3.5.3" } }, "sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw=="],
+
+ "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
+
+ "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="],
+
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
@@ -194,38 +709,294 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+ "node-addon-api": ["node-addon-api@8.7.0", "", {}, "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA=="],
+
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
+
+ "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
+
+ "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
+
+ "oslo": ["oslo@1.2.1", "", { "dependencies": { "@node-rs/argon2": "1.7.0", "@node-rs/bcrypt": "1.9.0" } }, "sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA=="],
+
+ "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
+
+ "papaparse": ["papaparse@5.5.3", "", {}, "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A=="],
+
+ "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
+
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "pdf-lib": ["pdf-lib@1.17.1", "", { "dependencies": { "@pdf-lib/standard-fonts": "^1.0.0", "@pdf-lib/upng": "^1.0.1", "pako": "^1.0.11", "tslib": "^1.11.1" } }, "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw=="],
+
+ "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="],
+
+ "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="],
+
+ "pg-connection-string": ["pg-connection-string@2.12.0", "", {}, "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ=="],
+
+ "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
+
+ "pg-pool": ["pg-pool@3.13.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA=="],
+
+ "pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="],
+
+ "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
+
+ "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
+ "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
+
+ "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
+
+ "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
+
+ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
+
+ "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
+
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
+ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
+
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
- "rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="],
+ "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
+
+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
+
+ "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
+
+ "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+
+ "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
- "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
+
+ "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
+
+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
+ "set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
+
+ "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
+
+ "simple-oauth2": ["simple-oauth2@5.1.0", "", { "dependencies": { "@hapi/hoek": "^11.0.4", "@hapi/wreck": "^18.0.0", "debug": "^4.3.4", "joi": "^17.6.4" } }, "sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw=="],
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
- "svelte": ["svelte@5.46.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.5.0", "esm-env": "^1.2.1", "esrap": "^2.2.1", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA=="],
+ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+
+ "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
+
+ "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
+
+ "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="],
+
+ "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
+
+ "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
+
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "svelte": ["svelte@5.55.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-z41M/hi0ZPTzrwVKLvB/R1/Oo08gL1uIib8HZ+FncqxxtY9MLb01emg2fqk+WLZ/lNrrtNDFh7BZLDxAHvMgLw=="],
"svelte-check": ["svelte-check@4.3.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q=="],
+ "svelte-marked": ["svelte-marked@0.8.0", "", { "dependencies": { "@types/marked": "6.0.0", "github-slugger": "2.0.0", "marked": "15.0.7" }, "peerDependencies": { "svelte": "^5" } }, "sha512-qOtPoMTZS4Syq+Tfx3Eux/M73lHETSQHrkgLPY6/LeTNQL3bQ+ckS/+DyV1B9HAy5KVUH/qxGypWiO7nsFMa1Q=="],
+
+ "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
+
+ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
+
+ "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+ "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
+
+ "tldts": ["tldts@7.0.28", "", { "dependencies": { "tldts-core": "^7.0.28" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw=="],
+
+ "tldts-core": ["tldts-core@7.0.28", "", {}, "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ=="],
+
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
+ "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="],
+
+ "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
+
+ "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
+
+ "tsparticles": ["tsparticles@3.9.1", "", { "dependencies": { "@tsparticles/engine": "3.9.1", "@tsparticles/interaction-external-trail": "3.9.1", "@tsparticles/plugin-absorbers": "3.9.1", "@tsparticles/plugin-emitters": "3.9.1", "@tsparticles/plugin-emitters-shape-circle": "3.9.1", "@tsparticles/plugin-emitters-shape-square": "3.9.1", "@tsparticles/shape-text": "3.9.1", "@tsparticles/slim": "3.9.1", "@tsparticles/updater-destroy": "3.9.1", "@tsparticles/updater-roll": "3.9.1", "@tsparticles/updater-tilt": "3.9.1", "@tsparticles/updater-twinkle": "3.9.1", "@tsparticles/updater-wobble": "3.9.1" } }, "sha512-Y780IGSL4qjkZj7+fI92PV/cziHqLR/s6nnYri4K6vH3NQRmDK5D6pfskDO8T4Y96ChCWHY3uxPtOb/hKQ83Qg=="],
+
+ "tsparticles-basic": ["tsparticles-basic@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0", "tsparticles-move-base": "^2.12.0", "tsparticles-shape-circle": "^2.12.0", "tsparticles-updater-color": "^2.12.0", "tsparticles-updater-opacity": "^2.12.0", "tsparticles-updater-out-modes": "^2.12.0", "tsparticles-updater-size": "^2.12.0" } }, "sha512-pN6FBpL0UsIUXjYbiui5+IVsbIItbQGOlwyGV55g6IYJBgdTNXgFX0HRYZGE9ZZ9psEXqzqwLM37zvWnb5AG9g=="],
+
+ "tsparticles-engine": ["tsparticles-engine@2.12.0", "", {}, "sha512-ZjDIYex6jBJ4iMc9+z0uPe7SgBnmb6l+EJm83MPIsOny9lPpetMsnw/8YJ3xdxn8hV+S3myTpTN1CkOVmFv0QQ=="],
+
+ "tsparticles-move-base": ["tsparticles-move-base@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-oSogCDougIImq+iRtIFJD0YFArlorSi8IW3HD2gO3USkH+aNn3ZqZNTqp321uB08K34HpS263DTbhLHa/D6BWw=="],
+
+ "tsparticles-plugin-emitters": ["tsparticles-plugin-emitters@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-fbskYnaXWXivBh9KFReVCfqHdhbNQSK2T+fq2qcGEWpwtDdgujcaS1k2Q/xjZnWNMfVesik4IrqspcL51gNdSA=="],
+
+ "tsparticles-plugin-sounds": ["tsparticles-plugin-sounds@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-Jm/Mdddq9E9VdHtAbYmv3pgtHQ3nBSbXN+n2axqm6vACgrE/JA4tgLZyuknns0scxibvqh/7zFxkOSa+4ucYuQ=="],
+
+ "tsparticles-preset-fireworks": ["tsparticles-preset-fireworks@2.12.0", "", { "dependencies": { "tsparticles-basic": "^2.12.0", "tsparticles-engine": "^2.12.0", "tsparticles-plugin-emitters": "^2.12.0", "tsparticles-plugin-sounds": "^2.12.0", "tsparticles-shape-line": "^2.12.0", "tsparticles-updater-destroy": "^2.12.0", "tsparticles-updater-life": "^2.12.0", "tsparticles-updater-rotate": "^2.12.0", "tsparticles-updater-stroke-color": "^2.12.0" } }, "sha512-8Y1F2YZH/LkqPVniEmfArHe2qyuJwzTiWmX1of9bClyA+Xz7p219JRednNp0QaAiZeJ+GS9X7hlBa4r3m1kPiw=="],
+
+ "tsparticles-shape-circle": ["tsparticles-shape-circle@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-L6OngbAlbadG7b783x16ns3+SZ7i0SSB66M8xGa5/k+YcY7zm8zG0uPt1Hd+xQDR2aNA3RngVM10O23/Lwk65Q=="],
+
+ "tsparticles-shape-line": ["tsparticles-shape-line@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-RcpKmmpKlk+R8mM5wA2v64Lv1jvXtU4SrBDv3vbdRodKbKaWGGzymzav1Q0hYyDyUZgplEK/a5ZwrfrOwmgYGA=="],
+
+ "tsparticles-updater-color": ["tsparticles-updater-color@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-KcG3a8zd0f8CTiOrylXGChBrjhKcchvDJjx9sp5qpwQK61JlNojNCU35xoaSk2eEHeOvFjh0o3CXWUmYPUcBTQ=="],
+
+ "tsparticles-updater-destroy": ["tsparticles-updater-destroy@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-6NN3dJhxACvzbIGL4dADbYQSZJmdHfwjujj1uvnxdMbb2x8C/AZzGxiN33smo4jkrZ5VLEWZWCJPJ8aOKjQ2Sg=="],
+
+ "tsparticles-updater-life": ["tsparticles-updater-life@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-J7RWGHAZkowBHpcLpmjKsxwnZZJ94oGEL2w+wvW1/+ZLmAiFFF6UgU0rHMC5CbHJT4IPx9cbkYMEHsBkcRJ0Bw=="],
+
+ "tsparticles-updater-opacity": ["tsparticles-updater-opacity@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-YUjMsgHdaYi4HN89LLogboYcCi1o9VGo21upoqxq19yRy0hRCtx2NhH22iHF/i5WrX6jqshN0iuiiNefC53CsA=="],
+
+ "tsparticles-updater-out-modes": ["tsparticles-updater-out-modes@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-owBp4Gk0JNlSrmp12XVEeBroDhLZU+Uq3szbWlHGSfcR88W4c/0bt0FiH5bHUqORIkw+m8O56hCjbqwj69kpOQ=="],
+
+ "tsparticles-updater-rotate": ["tsparticles-updater-rotate@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-waOFlGFmEZOzsQg4C4VSejNVXGf4dMf3fsnQrEROASGf1FCd8B6WcZau7JtXSTFw0OUGuk8UGz36ETWN72DkCw=="],
+
+ "tsparticles-updater-size": ["tsparticles-updater-size@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-B0yRdEDd/qZXCGDL/ussHfx5YJ9UhTqNvmS5X2rR2hiZhBAE2fmsXLeWkdtF2QusjPeEqFDxrkGiLOsh6poqRA=="],
+
+ "tsparticles-updater-stroke-color": ["tsparticles-updater-stroke-color@2.12.0", "", { "dependencies": { "tsparticles-engine": "^2.12.0" } }, "sha512-MPou1ZDxsuVq6SN1fbX+aI5yrs6FyP2iPCqqttpNbWyL+R6fik1rL0ab/x02B57liDXqGKYomIbBQVP3zUTW1A=="],
+
+ "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+ "undici": ["undici@7.24.7", "", {}, "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ=="],
+
+ "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
+
"vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="],
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="],
+ "vitest": ["vitest@4.1.4", "", { "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", "@vitest/pretty-format": "4.1.4", "@vitest/runner": "4.1.4", "@vitest/snapshot": "4.1.4", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.4", "@vitest/browser-preview": "4.1.4", "@vitest/browser-webdriverio": "4.1.4", "@vitest/coverage-istanbul": "4.1.4", "@vitest/coverage-v8": "4.1.4", "@vitest/ui": "4.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg=="],
+
+ "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
+
+ "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="],
+
+ "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="],
+
+ "whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="],
+
+ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
+
+ "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
+
+ "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
+
+ "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
+
+ "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
+
+ "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
+
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
+
+ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
+
+ "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+ "@emnapi/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@hapi/topo/@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="],
+
+ "@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
+
+ "@sideway/address/@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="],
+
+ "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
+
+ "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
+
+ "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "@types/marked/marked": ["marked@15.0.7", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg=="],
+
+ "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
+
+ "ast-v8-to-istanbul/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
+
+ "joi/@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="],
+
+ "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
+
+ "svelte/aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
+
+ "svelte-marked/marked": ["marked@15.0.7", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg=="],
+
+ "vite/rollup": ["rollup@4.54.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.54.0", "@rollup/rollup-android-arm64": "4.54.0", "@rollup/rollup-darwin-arm64": "4.54.0", "@rollup/rollup-darwin-x64": "4.54.0", "@rollup/rollup-freebsd-arm64": "4.54.0", "@rollup/rollup-freebsd-x64": "4.54.0", "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", "@rollup/rollup-linux-arm-musleabihf": "4.54.0", "@rollup/rollup-linux-arm64-gnu": "4.54.0", "@rollup/rollup-linux-arm64-musl": "4.54.0", "@rollup/rollup-linux-loong64-gnu": "4.54.0", "@rollup/rollup-linux-ppc64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-gnu": "4.54.0", "@rollup/rollup-linux-riscv64-musl": "4.54.0", "@rollup/rollup-linux-s390x-gnu": "4.54.0", "@rollup/rollup-linux-x64-gnu": "4.54.0", "@rollup/rollup-linux-x64-musl": "4.54.0", "@rollup/rollup-openharmony-arm64": "4.54.0", "@rollup/rollup-win32-arm64-msvc": "4.54.0", "@rollup/rollup-win32-ia32-msvc": "4.54.0", "@rollup/rollup-win32-x64-gnu": "4.54.0", "@rollup/rollup-win32-x64-msvc": "4.54.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw=="],
+
+ "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+
+ "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
+
+ "vite/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="],
+
+ "vite/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.54.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw=="],
+
+ "vite/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.54.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw=="],
+
+ "vite/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.54.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A=="],
+
+ "vite/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.54.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA=="],
+
+ "vite/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.54.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ=="],
+
+ "vite/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ=="],
+
+ "vite/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.54.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA=="],
+
+ "vite/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng=="],
+
+ "vite/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.54.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg=="],
+
+ "vite/rollup/@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw=="],
+
+ "vite/rollup/@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.54.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA=="],
+
+ "vite/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ=="],
+
+ "vite/rollup/@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.54.0", "", { "os": "linux", "cpu": "none" }, "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A=="],
+
+ "vite/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.54.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ=="],
+
+ "vite/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="],
+
+ "vite/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw=="],
+
+ "vite/rollup/@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.54.0", "", { "os": "none", "cpu": "arm64" }, "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg=="],
+
+ "vite/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.54.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw=="],
+
+ "vite/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.54.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ=="],
+
+ "vite/rollup/@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ=="],
+
+ "vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="],
}
}
diff --git a/resolution-frontend/drizzle/0002_add_package_type_and_orders.sql b/resolution-frontend/drizzle/0002_add_package_type_and_orders.sql
new file mode 100644
index 0000000..42e13d9
--- /dev/null
+++ b/resolution-frontend/drizzle/0002_add_package_type_and_orders.sql
@@ -0,0 +1,43 @@
+ALTER TABLE "warehouse_item" ADD COLUMN "package_type" text DEFAULT 'box' NOT NULL;
+--> statement-breakpoint
+CREATE TYPE "warehouse_order_status" AS ENUM ('DRAFT', 'ESTIMATED', 'APPROVED', 'SHIPPED', 'CANCELLED');
+--> statement-breakpoint
+CREATE TABLE "warehouse_order" (
+ "id" text PRIMARY KEY NOT NULL,
+ "created_by_id" text NOT NULL,
+ "status" "warehouse_order_status" DEFAULT 'DRAFT' NOT NULL,
+ "first_name" text NOT NULL,
+ "last_name" text NOT NULL,
+ "email" text NOT NULL,
+ "phone" text,
+ "address_line_1" text NOT NULL,
+ "address_line_2" text,
+ "city" text NOT NULL,
+ "state_province" text NOT NULL,
+ "postal_code" text,
+ "country" text NOT NULL,
+ "estimated_shipping_cents" integer,
+ "estimated_service_name" text,
+ "estimated_package_type" text,
+ "estimated_total_length_in" real,
+ "estimated_total_width_in" real,
+ "estimated_total_height_in" real,
+ "estimated_total_weight_grams" real,
+ "notes" text,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "warehouse_order_item" (
+ "id" text PRIMARY KEY NOT NULL,
+ "order_id" text NOT NULL,
+ "warehouse_item_id" text NOT NULL,
+ "quantity" integer DEFAULT 1 NOT NULL,
+ "sizing_choice" text
+);
+--> statement-breakpoint
+ALTER TABLE "warehouse_order" ADD CONSTRAINT "warehouse_order_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+--> statement-breakpoint
+ALTER TABLE "warehouse_order_item" ADD CONSTRAINT "warehouse_order_item_order_id_warehouse_order_id_fk" FOREIGN KEY ("order_id") REFERENCES "public"."warehouse_order"("id") ON DELETE cascade ON UPDATE no action;
+--> statement-breakpoint
+ALTER TABLE "warehouse_order_item" ADD CONSTRAINT "warehouse_order_item_warehouse_item_id_warehouse_item_id_fk" FOREIGN KEY ("warehouse_item_id") REFERENCES "public"."warehouse_item"("id") ON DELETE restrict ON UPDATE no action;
diff --git a/resolution-frontend/drizzle/0003_add_templates_and_batches.sql b/resolution-frontend/drizzle/0003_add_templates_and_batches.sql
new file mode 100644
index 0000000..7044b7e
--- /dev/null
+++ b/resolution-frontend/drizzle/0003_add_templates_and_batches.sql
@@ -0,0 +1,55 @@
+-- Order Templates
+CREATE TABLE "warehouse_order_template" (
+ "id" text PRIMARY KEY NOT NULL,
+ "created_by_id" text NOT NULL,
+ "name" text NOT NULL,
+ "is_public" boolean DEFAULT false NOT NULL,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "warehouse_order_template_item" (
+ "id" text PRIMARY KEY NOT NULL,
+ "template_id" text NOT NULL,
+ "warehouse_item_id" text NOT NULL,
+ "quantity" integer DEFAULT 1 NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "warehouse_order_template" ADD CONSTRAINT "warehouse_order_template_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+--> statement-breakpoint
+ALTER TABLE "warehouse_order_template_item" ADD CONSTRAINT "warehouse_order_template_item_template_id_warehouse_order_template_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."warehouse_order_template"("id") ON DELETE cascade ON UPDATE no action;
+--> statement-breakpoint
+ALTER TABLE "warehouse_order_template_item" ADD CONSTRAINT "warehouse_order_template_item_warehouse_item_id_warehouse_item_id_fk" FOREIGN KEY ("warehouse_item_id") REFERENCES "public"."warehouse_item"("id") ON DELETE restrict ON UPDATE no action;
+--> statement-breakpoint
+-- Batches
+CREATE TYPE "warehouse_batch_status" AS ENUM ('AWAITING_MAPPING', 'MAPPED', 'PROCESSED');
+--> statement-breakpoint
+CREATE TABLE "warehouse_batch" (
+ "id" text PRIMARY KEY NOT NULL,
+ "created_by_id" text NOT NULL,
+ "template_id" text NOT NULL,
+ "title" text,
+ "status" "warehouse_batch_status" DEFAULT 'AWAITING_MAPPING' NOT NULL,
+ "csv_data" text NOT NULL,
+ "field_mapping" text,
+ "address_count" integer DEFAULT 0 NOT NULL,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "warehouse_batch_tag" (
+ "id" text PRIMARY KEY NOT NULL,
+ "batch_id" text NOT NULL,
+ "tag" text NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "warehouse_batch" ADD CONSTRAINT "warehouse_batch_created_by_id_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
+--> statement-breakpoint
+ALTER TABLE "warehouse_batch" ADD CONSTRAINT "warehouse_batch_template_id_warehouse_order_template_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."warehouse_order_template"("id") ON DELETE restrict ON UPDATE no action;
+--> statement-breakpoint
+ALTER TABLE "warehouse_batch_tag" ADD CONSTRAINT "warehouse_batch_tag_batch_id_warehouse_batch_id_fk" FOREIGN KEY ("batch_id") REFERENCES "public"."warehouse_batch"("id") ON DELETE cascade ON UPDATE no action;
+--> statement-breakpoint
+CREATE UNIQUE INDEX "warehouse_batch_tag_unique_idx" ON "warehouse_batch_tag" USING btree ("batch_id","tag");
+--> statement-breakpoint
+-- Add batch_id to warehouse_order
+ALTER TABLE "warehouse_order" ADD COLUMN "batch_id" text;
diff --git a/resolution-frontend/drizzle/0004_add_label_tracking_fields.sql b/resolution-frontend/drizzle/0004_add_label_tracking_fields.sql
new file mode 100644
index 0000000..7b1af83
--- /dev/null
+++ b/resolution-frontend/drizzle/0004_add_label_tracking_fields.sql
@@ -0,0 +1,3 @@
+ALTER TABLE "warehouse_order" ADD COLUMN "tracking_number" text;
+ALTER TABLE "warehouse_order" ADD COLUMN "label_url" text;
+ALTER TABLE "warehouse_order" ADD COLUMN "shipping_method" text;
diff --git a/resolution-frontend/drizzle/0005_add_hs_code.sql b/resolution-frontend/drizzle/0005_add_hs_code.sql
new file mode 100644
index 0000000..0260b5a
--- /dev/null
+++ b/resolution-frontend/drizzle/0005_add_hs_code.sql
@@ -0,0 +1 @@
+ALTER TABLE "warehouse_item" ADD COLUMN "hs_code" text NOT NULL DEFAULT '';
diff --git a/resolution-frontend/drizzle/meta/0002_snapshot.json b/resolution-frontend/drizzle/meta/0002_snapshot.json
index fb049ac..1cebc5d 100644
--- a/resolution-frontend/drizzle/meta/0002_snapshot.json
+++ b/resolution-frontend/drizzle/meta/0002_snapshot.json
@@ -1,5 +1,5 @@
{
- "id": "35dc54d2-783c-4816-8106-c7e56ffd8939",
+ "id": "0fa6b235-2de1-4477-8997-b6da9895316e",
"prevId": "becd34cd-7692-4ef3-a912-458e58e190ad",
"version": "7",
"dialect": "postgresql",
@@ -1058,6 +1058,90 @@
"checkConstraints": {},
"isRLSEnabled": false
},
+ "public.warehouse_item": {
+ "name": "warehouse_item",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sku": {
+ "name": "sku",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sizing": {
+ "name": "sizing",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weight_grams": {
+ "name": "weight_grams",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cost_cents": {
+ "name": "cost_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "quantity": {
+ "name": "quantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "image_filename": {
+ "name": "image_filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "warehouse_item_sku_unique": {
+ "name": "warehouse_item_sku_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "sku"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
"public.weekly_ship": {
"name": "weekly_ship",
"schema": "",
@@ -1564,6 +1648,7 @@
"schema": "public",
"values": [
"PYTHON",
+ "WEB_DEV",
"RUST",
"GAME_DEV",
"HARDWARE",
diff --git a/resolution-frontend/drizzle/meta/_journal.json b/resolution-frontend/drizzle/meta/_journal.json
index 22a6c0c..75c06c7 100644
--- a/resolution-frontend/drizzle/meta/_journal.json
+++ b/resolution-frontend/drizzle/meta/_journal.json
@@ -19,8 +19,15 @@
{
"idx": 2,
"version": "7",
- "when": 1772636037511,
- "tag": "0002_luxuriant_maria_hill",
+ "when": 1772206185519,
+ "tag": "0002_add_package_type_and_orders",
+ "breakpoints": true
+ },
+ {
+ "idx": 3,
+ "version": "7",
+ "when": 1772790000000,
+ "tag": "0003_add_templates_and_batches",
"breakpoints": true
}
]
diff --git a/resolution-frontend/entrypoint.sh b/resolution-frontend/entrypoint.sh
new file mode 100644
index 0000000..ef44e53
--- /dev/null
+++ b/resolution-frontend/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+set -e
+
+echo "Running database schema push..."
+echo "" | npx drizzle-kit push --verbose 2>&1 || echo "WARNING: drizzle-kit push failed with exit code $?"
+echo "Schema push complete."
+
+exec node build
diff --git a/resolution-frontend/package-lock.json b/resolution-frontend/package-lock.json
index 6ecccad..e8bdf3d 100644
--- a/resolution-frontend/package-lock.json
+++ b/resolution-frontend/package-lock.json
@@ -17,19 +17,21 @@
"airtable": "^0.12.2",
"bcrypt": "^6.0.0",
"canvas-confetti": "^1.9.4",
- "dompurify": "^3.3.2",
+ "dompurify": "^3.3.3",
"dotenv": "^16.4.0",
"drizzle-orm": "^0.45.1",
"jsonwebtoken": "^9.0.3",
"lucia": "^3.2.2",
"monaco-editor": "^0.55.1",
"oslo": "^1.2.1",
+ "pdf-lib": "^1.17.1",
"pg": "^8.13.0",
"simple-oauth2": "^5.1.0",
"svelte-marked": "^0.8.0",
"tsparticles": "^3.9.1",
"tsparticles-preset-fireworks": "^2.12.0",
"uuid": "^13.0.0",
+ "xml2js": "^0.6.2",
"zod": "^4.3.5"
},
"devDependencies": {
@@ -44,6 +46,7 @@
"@types/jsonwebtoken": "^9.0.10",
"@types/pg": "^8.16.0",
"@types/simple-oauth2": "^5.0.8",
+ "@types/xml2js": "^0.4.14",
"@vitest/coverage-v8": "^4.0.18",
"drizzle-kit": "^0.31.8",
"jsdom": "^28.1.0",
@@ -292,7 +295,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
},
@@ -333,7 +335,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -1516,6 +1517,24 @@
"cuid2": "bin/cuid2.js"
}
},
+ "node_modules/@pdf-lib/standard-fonts": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
+ "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^1.0.6"
+ }
+ },
+ "node_modules/@pdf-lib/upng": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
+ "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^1.0.10"
+ }
+ },
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"dev": true,
@@ -2046,7 +2065,6 @@
"integrity": "sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5",
@@ -2087,7 +2105,6 @@
"version": "6.2.1",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"debug": "^4.4.1",
@@ -2975,7 +2992,6 @@
"integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
@@ -3006,6 +3022,16 @@
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"license": "MIT"
},
+ "node_modules/@types/xml2js": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
+ "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@vitest/coverage-v8": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz",
@@ -3179,7 +3205,6 @@
"node_modules/acorn": {
"version": "8.15.0",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3588,7 +3613,6 @@
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.1.tgz",
"integrity": "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==",
"license": "Apache-2.0",
- "peer": true,
"peerDependencies": {
"@aws-sdk/client-rds-data": ">=3",
"@cloudflare/workers-types": ">=4",
@@ -3751,7 +3775,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -4284,7 +4307,6 @@
"integrity": "sha512-P1FlFBGCMPMXu+EGdVD9W4Mjm0DqsusmKgO7Xc33mI5X1bklmsQb0hfzPhXomQr9waWIBDsiOjvr1e6BTaUqpA==",
"deprecated": "This package has been deprecated. Please see https://lucia-auth.com/lucia-v3/migrate.",
"license": "MIT",
- "peer": true,
"dependencies": {
"@oslojs/crypto": "^1.0.1",
"@oslojs/encoding": "^1.1.0"
@@ -4508,6 +4530,12 @@
"@node-rs/bcrypt": "1.9.0"
}
},
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/parse5": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
@@ -4535,12 +4563,29 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pdf-lib": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
+ "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@pdf-lib/standard-fonts": "^1.0.0",
+ "@pdf-lib/upng": "^1.0.1",
+ "pako": "^1.0.11",
+ "tslib": "^1.11.1"
+ }
+ },
+ "node_modules/pdf-lib/node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "license": "0BSD"
+ },
"node_modules/pg": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
"integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"pg-connection-string": "^2.11.0",
"pg-pool": "^3.11.0",
@@ -4812,7 +4857,6 @@
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -4883,6 +4927,15 @@
],
"license": "MIT"
},
+ "node_modules/sax": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
+ "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -5049,7 +5102,6 @@
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.53.6.tgz",
"integrity": "sha512-lP5DGF3oDDI9fhHcSpaBiJEkFLuS16h92DhM1L5K1lFm0WjOmUh1i2sNkBBk8rkxJRpob0dBE75jRfUzGZUOGA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -5485,7 +5537,6 @@
"version": "5.9.3",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5528,7 +5579,6 @@
"version": "7.3.0",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -5622,7 +5672,6 @@
"integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@vitest/expect": "4.0.18",
"@vitest/mocker": "4.0.18",
@@ -5761,6 +5810,28 @@
"node": ">=18"
}
},
+ "node_modules/xml2js": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
+ "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
diff --git a/resolution-frontend/package.json b/resolution-frontend/package.json
index c36fcea..bd7f3b0 100644
--- a/resolution-frontend/package.json
+++ b/resolution-frontend/package.json
@@ -23,8 +23,10 @@
"@types/bcrypt": "^6.0.0",
"@types/dompurify": "^3.2.0",
"@types/jsonwebtoken": "^9.0.10",
+ "@types/papaparse": "^5.5.2",
"@types/pg": "^8.16.0",
"@types/simple-oauth2": "^5.0.8",
+ "@types/xml2js": "^0.4.14",
"@vitest/coverage-v8": "^4.0.18",
"drizzle-kit": "^0.31.8",
"jsdom": "^28.1.0",
@@ -44,19 +46,22 @@
"airtable": "^0.12.2",
"bcrypt": "^6.0.0",
"canvas-confetti": "^1.9.4",
- "dompurify": "^3.3.2",
+ "dompurify": "^3.3.3",
"dotenv": "^16.4.0",
"drizzle-orm": "^0.45.1",
"jsonwebtoken": "^9.0.3",
"lucia": "^3.2.2",
"monaco-editor": "^0.55.1",
"oslo": "^1.2.1",
+ "papaparse": "^5.5.3",
+ "pdf-lib": "^1.17.1",
"pg": "^8.13.0",
"simple-oauth2": "^5.1.0",
"svelte-marked": "^0.8.0",
"tsparticles": "^3.9.1",
"tsparticles-preset-fireworks": "^2.12.0",
"uuid": "^13.0.0",
+ "xml2js": "^0.6.2",
"zod": "^4.3.5"
},
"overrides": {
diff --git a/resolution-frontend/src/app.html b/resolution-frontend/src/app.html
index 52d36b8..bf39263 100644
--- a/resolution-frontend/src/app.html
+++ b/resolution-frontend/src/app.html
@@ -7,6 +7,7 @@
+
%sveltekit.head%
diff --git a/resolution-frontend/src/lib/server/canada-post.ts b/resolution-frontend/src/lib/server/canada-post.ts
new file mode 100644
index 0000000..7374cf2
--- /dev/null
+++ b/resolution-frontend/src/lib/server/canada-post.ts
@@ -0,0 +1,736 @@
+import { env } from '$env/dynamic/private';
+import xml2js from 'xml2js';
+import { PDFDocument } from 'pdf-lib';
+import { fetchChitChatsRates } from './chit-chats';
+import { resolveStateCode } from './countries';
+import { getCadToUsdRate } from './exchange-rate';
+import { arrayBufferToBase64 } from './utils';
+
+export const INCHES_TO_CM = 2.54;
+export const GRAMS_TO_KG = 0.001;
+
+export function escapeXml(str: string): string {
+ return str
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
+/** Format HS code to Canada Post format ####.##.##.## (6, 8 or 10 digits with dots) */
+function formatHsTariffCode(code: string | null | undefined): string {
+ if (!code) return '';
+ const digits = code.replace(/[^0-9]/g, '');
+ if (digits.length < 6) return '';
+ // ####.## (6 digits)
+ let formatted = digits.substring(0, 4) + '.' + digits.substring(4, 6);
+ // ####.##.## (8 digits)
+ if (digits.length >= 8) formatted += '.' + digits.substring(6, 8);
+ // ####.##.##.## (10 digits)
+ if (digits.length >= 10) formatted += '.' + digits.substring(8, 10);
+ return formatted;
+}
+
+async function cropLabelTo4x6(pdfBuffer: ArrayBuffer): Promise {
+ const srcDoc = await PDFDocument.load(pdfBuffer);
+ const page = srcDoc.getPage(0);
+
+ // Canada Post non-contract labels are landscape 792x612 (11x8.5in)
+ // Label content is in the right portion of the page
+ // Crop tightly to the label borders for thermal printing
+ const cropX = 448;
+ const cropY = 100;
+ const cropWidth = 330;
+ const cropHeight = 445;
+
+ page.setMediaBox(cropX, cropY, cropWidth, cropHeight);
+ page.setCropBox(cropX, cropY, cropWidth, cropHeight);
+ // Do NOT call page.setSize() - it overrides MediaBox and breaks the crop
+
+ return await srcDoc.save();
+}
+
+export function inchesToCm(inches: number): number {
+ return Math.round(inches * INCHES_TO_CM * 10) / 10;
+}
+
+export function isLettermail(serviceName: string | null): boolean {
+ if (!serviceName) return false;
+ const lower = serviceName.toLowerCase();
+ return lower.includes('lettermail') || lower.includes('bubble packet');
+}
+
+export function buildDestinationXml(country: string, postalCode?: string, stateCode?: string): string {
+ if (country === 'CA') {
+ return `
+ ${(postalCode ?? '').replace(/\s/g, '').toUpperCase()}
+ `;
+ } else if (country === 'US') {
+ return `
+ ${(postalCode ?? '').replace(/\s/g, '')}
+ ${stateCode ? `${escapeXml(stateCode)}` : ''}
+ `;
+ } else {
+ return `
+ ${escapeXml(country)}
+ ${postalCode ? `${escapeXml(postalCode)}` : ''}
+ `;
+ }
+}
+
+export function buildRateRequestXml(params: {
+ originPostal: string;
+ country: string;
+ postalCode?: string;
+ weightKg: number;
+ lengthCm: number;
+ widthCm: number;
+ heightCm: number;
+}): string {
+ const { originPostal, country, postalCode, weightKg, lengthCm, widthCm, heightCm } = params;
+ return `
+
+ ${env.CP_CUSTOMER_NUMBER}
+ ${env.CP_CONTRACT_ID ? `${env.CP_CONTRACT_ID}` : ''}
+
+ ${Math.round(weightKg * 100) / 100}
+
+ ${lengthCm}
+ ${widthCm}
+ ${heightCm}
+
+
+ ${originPostal.replace(/\s/g, '').toUpperCase()}
+
+ ${buildDestinationXml(country, postalCode)}
+
+`;
+}
+
+export function buildCreateShipmentXml(params: {
+ order: any;
+ weightKg: number;
+ lengthCm: number;
+ widthCm: number;
+ heightCm: number;
+ serviceCode: string;
+}): string {
+ const { order, weightKg, lengthCm, widthCm, heightCm, serviceCode } = params;
+ const originPostal = (env.CP_ORIGIN_POSTAL_CODE || '').replace(/\s/g, '').toUpperCase();
+ const customerNumber = env.CP_CUSTOMER_NUMBER;
+ const contractId = env.CP_CONTRACT_ID;
+
+ const destinationXml = buildDestinationXml(order.country, order.postalCode, order.stateProvince);
+
+ let customsXml = '';
+ if (order.country !== 'CA') {
+ const items = (order.items || []).filter((oi: any) => oi.warehouseItem);
+ let skuLines: string;
+ if (items.length > 0) {
+ skuLines = items.map((oi: any) => {
+ const item = oi.warehouseItem;
+ const unitWeightKg = Math.round(item.weightGrams * GRAMS_TO_KG * 1000) / 1000;
+ const valuePerUnit = Math.round(item.costCents) / 100;
+ return `-
+ ${oi.quantity}
+ ${escapeXml(item.name.substring(0, 45))}
+ ${escapeXml((item.sku || '').substring(0, 15))}
+ ${escapeXml(formatHsTariffCode(item.hsCode))}
+ ${unitWeightKg}
+ ${valuePerUnit.toFixed(2)}
+ CA
+
`;
+ }).join('\n');
+ } else {
+ skuLines = `-
+ 1
+ Merchandise
+ ${Math.max(0.01, Math.round(weightKg * 1000) / 1000)}
+ 1.00
+ CA
+
`;
+ }
+
+ customsXml = `
+ CAD
+ SOG
+ Merchandise
+ ${skuLines}
+ `;
+ }
+
+ const deliverySpecXml = `
+ ${escapeXml(serviceCode)}
+
+ ${escapeXml(env.CP_SENDER_NAME || 'Hack Club')}
+ ${escapeXml(env.CP_SENDER_NAME || 'Hack Club')}
+ ${escapeXml(env.CP_SENDER_PHONE || '000-000-0000')}
+
+ ${escapeXml(env.CP_SENDER_ADDRESS || '')}
+ ${env.CP_SENDER_ADDRESS_2 ? `${escapeXml(env.CP_SENDER_ADDRESS_2)}` : ''}
+ ${escapeXml(env.CP_SENDER_CITY || '')}
+ ${escapeXml(env.CP_SENDER_PROVINCE || '')}
+ ${contractId ? 'CA' : ''}
+ ${originPostal}
+
+
+
+ ${escapeXml(`${order.firstName} ${order.lastName}`.substring(0, 44))}
+ ${escapeXml((order.phone || env.CP_SENDER_PHONE || '000-000-0000').substring(0, 25))}
+
+ ${escapeXml((order.addressLine1 || '').substring(0, 44))}
+ ${(order.addressLine2 || order.addressLine1?.length > 44) ? `${escapeXml((order.addressLine2 || order.addressLine1?.substring(44) || '').substring(0, 44))}` : ''}
+ ${escapeXml((order.city || '').substring(0, 40))}
+ ${escapeXml(resolveStateCode(order.stateProvince || '').substring(0, 20))}
+ ${escapeXml(order.country)}
+ ${(order.postalCode ?? '').replace(/\s/g, '').toUpperCase()}
+
+
+ ${order.country !== 'CA' ? `
+
+ ` : ''}
+
+ ${Math.max(0.01, Math.round(weightKg * 1000) / 1000)}
+
+ ${Math.max(1, lengthCm)}
+ ${Math.max(1, widthCm)}
+ ${Math.max(1, heightCm)}
+
+
+ ${contractId ? `
+ 4x6
+ PDF
+ ` : ''}
+
+ false
+
+ ${customsXml}
+ ${contractId ? `
+ ${customerNumber}
+ ${contractId}
+ Account
+ ` : ''}
+ `;
+
+ if (contractId) {
+ return `
+
+
+ ${originPostal}
+ true
+ ${deliverySpecXml}
+`;
+ }
+
+ return `
+
+ ${originPostal}
+ ${deliverySpecXml}
+`;
+}
+
+export function getServiceCode(serviceName: string): string {
+ const lower = serviceName.toLowerCase();
+ if (lower.includes('priority')) return 'DOM.PC';
+ if (lower.includes('xpresspost') && lower.includes('international')) return 'INT.XP';
+ if (lower.includes('xpresspost')) return 'DOM.XP';
+ if (lower.includes('expedited') && lower.includes('usa')) return 'USA.EP';
+ if (lower.includes('expedited')) return 'DOM.EP';
+ if (lower.includes('regular') && lower.includes('usa')) return 'USA.PW.ENV';
+ if (lower.includes('regular')) return 'DOM.RP';
+ if (lower.includes('small packet') && lower.includes('usa')) return 'USA.SP.AIR';
+ if (lower.includes('small packet') && lower.includes('surface')) return 'INT.SP.SURF';
+ if (lower.includes('small packet') && lower.includes('air')) return 'INT.SP.AIR';
+ if (lower.includes('tracked packet') && lower.includes('usa')) return 'USA.TP';
+ if (lower.includes('tracked packet')) return 'INT.TP';
+ if (lower.includes('international') && lower.includes('parcel') && lower.includes('surface')) return 'INT.IP.SURF';
+ if (lower.includes('international') && lower.includes('parcel') && lower.includes('air')) return 'INT.IP';
+ if (lower.includes('surface') && lower.includes('international')) return 'INT.SP.SURF';
+ if (lower.includes('air') && lower.includes('international')) return 'INT.SP.AIR';
+ if (lower.includes('u.s.') || lower.includes('usa')) return 'USA.TP';
+ if (lower.includes('international')) return 'INT.TP';
+ return 'DOM.RP';
+}
+
+export interface RateOption {
+ serviceName: string;
+ serviceCode: string;
+ priceDetails: { base: number; gst: number; pst: number; hst: number; total: number };
+ deliveryDate: string;
+ transitDays: string;
+ currency: string;
+}
+
+interface LetterMailOption {
+ serviceName: string;
+ serviceCode: string;
+ priceDetails: { base: number; gst: number; pst: number; hst: number; total: number };
+ deliveryDate: string;
+ transitDays: string;
+ isLettermail: boolean;
+ note: string;
+}
+
+export function getLetterMailOptions(
+ weightGrams: number,
+ lengthCm: number,
+ widthCm: number,
+ heightCm: number,
+ country: string
+): LetterMailOption[] {
+ const options: LetterMailOption[] = [];
+
+ const lengthMm = lengthCm * 10;
+ const widthMm = widthCm * 10;
+ const heightMm = heightCm * 10;
+
+ const meetsMinDimensions = lengthMm >= 140 && widthMm >= 90;
+ const isStandardSize = lengthMm <= 245 && widthMm <= 156 && heightMm <= 5;
+ const isOversizeSize = lengthMm <= 380 && widthMm <= 270 && heightMm <= 20;
+
+ // Lettermail prices from Canada Post's published rate card (updated periodically)
+ if (meetsMinDimensions && isStandardSize && weightGrams <= 30 && weightGrams >= 2) {
+ let price: number;
+ if (country === 'CA') price = 1.75;
+ else if (country === 'US') price = 2.0;
+ else price = 3.5;
+
+ const countryLabel = country === 'CA' ? 'Domestic' : country === 'US' ? 'USA' : 'International';
+ options.push({
+ serviceName: `Lettermail ${countryLabel} (up to 30g)`,
+ serviceCode: 'LETTERMAIL.STD',
+ priceDetails: { base: price, gst: 0, pst: 0, hst: 0, total: price },
+ deliveryDate: 'N/A',
+ transitDays: country === 'CA' ? '2-4' : country === 'US' ? '4-7' : '7-14',
+ isLettermail: true,
+ note: 'Max: 245mm x 156mm x 5mm'
+ });
+ }
+
+ // Bubble packet prices from Canada Post's published rate card (updated periodically)
+ if (isOversizeSize && weightGrams >= 5 && weightGrams <= 500) {
+ let price: number;
+ const countryLabel = country === 'CA' ? 'Domestic' : country === 'US' ? 'USA' : 'International';
+
+ if (country === 'CA') {
+ if (weightGrams <= 100) price = 3.11;
+ else if (weightGrams <= 200) price = 4.51;
+ else if (weightGrams <= 300) price = 5.91;
+ else if (weightGrams <= 400) price = 6.62;
+ else price = 7.05;
+ } else if (country === 'US') {
+ if (weightGrams <= 100) price = 4.51;
+ else if (weightGrams <= 200) price = 7.16;
+ else price = 13.38;
+ } else {
+ if (weightGrams <= 100) price = 8.08;
+ else if (weightGrams <= 200) price = 13.38;
+ else price = 25.80;
+ }
+
+ options.push({
+ serviceName: `Bubble Packet ${countryLabel} (up to 500g)`,
+ serviceCode: 'BUBBLE.PACKET',
+ priceDetails: { base: price, gst: 0, pst: 0, hst: 0, total: price },
+ deliveryDate: 'N/A',
+ transitDays: country === 'CA' ? '2-5' : country === 'US' ? '5-10' : '10-21',
+ isLettermail: true,
+ note: 'Max: 380mm x 270mm x 20mm'
+ });
+ }
+
+ return options;
+}
+
+export function getCanadaPostConfig(): { baseUrl: string; authHeader: string; customerNumber: string } {
+ const username = env.CP_API_USERNAME;
+ const password = env.CP_API_PASSWORD;
+ const customerNumber = env.CP_CUSTOMER_NUMBER;
+
+ if (!username || !password || !customerNumber) {
+ throw new Error('Canada Post API not configured (CP_API_USERNAME, CP_API_PASSWORD, CP_CUSTOMER_NUMBER required)');
+ }
+
+ const baseUrl = env.CP_ENVIRONMENT === 'production'
+ ? 'https://soa-gw.canadapost.ca'
+ : 'https://ct.soa-gw.canadapost.ca';
+
+ const authHeader = `Basic ${btoa(`${username}:${password}`)}`;
+
+ return { baseUrl, authHeader, customerNumber };
+}
+
+interface PriceQuote {
+ 'service-name': string;
+ 'service-code': string;
+ 'price-details': {
+ base?: string;
+ due?: string;
+ taxes?: {
+ gst?: string | { $: string };
+ pst?: string | { $: string };
+ hst?: string | { $: string };
+ };
+ };
+ 'service-standard'?: {
+ 'expected-delivery-date'?: string;
+ 'expected-transit-time'?: string;
+ };
+}
+
+function getTaxValue(tax: string | { $: string } | undefined): number {
+ if (!tax) return 0;
+ if (typeof tax === 'string') return parseFloat(tax) || 0;
+ return parseFloat(tax.$) || 0;
+}
+
+export async function fetchRates(params: {
+ country: string;
+ postalCode?: string;
+ weightKg: number;
+ lengthCm: number;
+ widthCm: number;
+ heightCm: number;
+}): Promise {
+ const { baseUrl, authHeader } = getCanadaPostConfig();
+ const originPostal = env.CP_ORIGIN_POSTAL_CODE;
+ if (!originPostal) {
+ throw new Error('CP_ORIGIN_POSTAL_CODE is required');
+ }
+
+ const xmlBody = buildRateRequestXml({
+ originPostal,
+ country: params.country,
+ postalCode: params.postalCode,
+ weightKg: params.weightKg,
+ lengthCm: params.lengthCm,
+ widthCm: params.widthCm,
+ heightCm: params.heightCm
+ });
+
+ const response = await fetch(`${baseUrl}/rs/ship/price`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/vnd.cpc.ship.rate-v4+xml',
+ Accept: 'application/vnd.cpc.ship.rate-v4+xml',
+ Authorization: authHeader,
+ 'Accept-language': 'en-CA'
+ },
+ body: xmlBody
+ });
+
+ const xmlResponse = await response.text();
+
+ if (!response.ok) {
+ console.error('Canada Post API error:', xmlResponse);
+ return [];
+ }
+
+ const parser = new xml2js.Parser({ explicitArray: false });
+ const result = await parser.parseStringPromise(xmlResponse);
+ const cadToUsd = await getCadToUsdRate();
+ const handlingFee = 2.0;
+
+ const priceQuotes = result['price-quotes'] as { 'price-quote'?: PriceQuote | PriceQuote[] } | undefined;
+ if (!priceQuotes?.['price-quote']) return [];
+
+ let quotes = priceQuotes['price-quote'];
+ if (!Array.isArray(quotes)) quotes = [quotes];
+
+ return quotes.map((quote) => {
+ const priceDetails = quote['price-details'];
+ const taxes = priceDetails.taxes ?? {};
+ const baseTotalCAD = parseFloat(priceDetails.due ?? '0');
+ const totalCAD = baseTotalCAD + handlingFee;
+ const totalUSD = Math.round(totalCAD * cadToUsd * 100) / 100;
+
+ return {
+ serviceName: quote['service-name'],
+ serviceCode: quote['service-code'],
+ priceDetails: {
+ base: Math.round(parseFloat(priceDetails.base ?? '0') * cadToUsd * 100) / 100,
+ gst: Math.round(getTaxValue(taxes.gst) * cadToUsd * 100) / 100,
+ pst: Math.round(getTaxValue(taxes.pst) * cadToUsd * 100) / 100,
+ hst: Math.round(getTaxValue(taxes.hst) * cadToUsd * 100) / 100,
+ total: totalUSD
+ },
+ deliveryDate: quote['service-standard']?.['expected-delivery-date'] ?? 'N/A',
+ transitDays: quote['service-standard']?.['expected-transit-time'] ?? 'N/A',
+ currency: 'USD'
+ };
+ });
+}
+
+
+export async function createShipment(params: {
+ order: any;
+ weightKg: number;
+ lengthCm: number;
+ widthCm: number;
+ heightCm: number;
+ serviceCode: string;
+}): Promise<{ trackingPin: string | null; labelBase64: string | null }> {
+ const { baseUrl, authHeader, customerNumber } = getCanadaPostConfig();
+
+ const shipmentXml = buildCreateShipmentXml(params);
+
+ const contractId = env.CP_CONTRACT_ID;
+ const cpEndpoint = contractId
+ ? `${baseUrl}/rs/${customerNumber}/${customerNumber}/shipment`
+ : `${baseUrl}/rs/${customerNumber}/ncshipment`;
+
+ // No retry loop: retrying shipment creation risks duplicate labels and double-charging carriers
+ const cpRes = await fetch(cpEndpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': contractId ? 'application/vnd.cpc.shipment-v8+xml' : 'application/vnd.cpc.ncshipment-v4+xml',
+ Accept: contractId ? 'application/vnd.cpc.shipment-v8+xml' : 'application/vnd.cpc.ncshipment-v4+xml',
+ Authorization: authHeader,
+ 'Accept-language': 'en-CA',
+ ...(params.order.country === 'US' && env.ZONOS_ACCOUNT_KEY ? { 'X-CPC-Zonos-Key': env.ZONOS_ACCOUNT_KEY } : {})
+ },
+ body: shipmentXml
+ });
+
+ if (!cpRes.ok) {
+ const errText = await cpRes.text();
+ console.error('Canada Post Create Shipment error:', errText);
+ throw new Error(`Canada Post shipment creation failed: ${cpRes.status}`);
+ }
+
+ const cpXml = await cpRes!.text();
+ const parser = new xml2js.Parser({ explicitArray: false });
+ const cpResult = await parser.parseStringPromise(cpXml);
+ const shipmentInfo = cpResult['shipment-info'] || cpResult['non-contract-shipment-info'];
+
+ const trackingPin: string | null = shipmentInfo?.['tracking-pin'] || null;
+ let labelBase64: string | null = null;
+
+ const links = shipmentInfo?.links?.link;
+ if (links) {
+ const linkArray = Array.isArray(links) ? links : [links];
+ const labelLink = linkArray.find((l: any) => l.$?.rel === 'label');
+ if (labelLink?.$?.href) {
+ const labelRes = await fetch(labelLink.$.href, {
+ headers: {
+ Accept: 'application/pdf',
+ Authorization: authHeader
+ }
+ });
+ if (labelRes.ok) {
+ let labelBuffer: ArrayBuffer | Uint8Array = await labelRes.arrayBuffer();
+ if (!contractId) {
+ labelBuffer = await cropLabelTo4x6(labelBuffer);
+ }
+ labelBase64 = `data:application/pdf;base64,${arrayBufferToBase64(labelBuffer)}`;
+ }
+ }
+ }
+
+ return { trackingPin, labelBase64 };
+}
+
+export interface ZonosDutyResult {
+ duties: number;
+ taxes: number;
+ fees: number;
+ total: number;
+ currency: string;
+}
+
+export async function calculateZonosDuties(params: {
+ items: Array<{ hsCode: string; valueCadCents: number; quantity: number; sku: string; description: string }>;
+ shippingCostCad: number;
+ destinationAddress: { city: string; state: string; postalCode: string; country: string };
+ serviceLevelCode?: string;
+}): Promise {
+ const credentialToken = env.ZONOS_CREDENTIAL_TOKEN;
+ if (!credentialToken) return null;
+
+ const originPostal = (env.CP_ORIGIN_POSTAL_CODE || '').replace(/\s/g, '').toUpperCase();
+
+ const parties = [
+ {
+ type: 'ORIGIN',
+ location: {
+ countryCode: 'CA',
+ postalCode: originPostal
+ }
+ },
+ {
+ type: 'DESTINATION',
+ location: {
+ countryCode: params.destinationAddress.country,
+ administrativeArea: params.destinationAddress.state,
+ city: params.destinationAddress.city,
+ postalCode: params.destinationAddress.postalCode
+ }
+ }
+ ];
+
+ const items = params.items.map((item) => ({
+ amount: item.valueCadCents / 100,
+ currencyCode: 'CAD',
+ quantity: item.quantity,
+ hsCode: item.hsCode || undefined,
+ sku: item.sku || undefined,
+ description: item.description,
+ countryOfOrigin: 'CA',
+ productId: item.sku || undefined
+ }));
+
+ const shipmentRating = [
+ {
+ amount: params.shippingCostCad,
+ currencyCode: 'CAD',
+ serviceLevelCode: params.serviceLevelCode || 'standard'
+ }
+ ];
+
+ const landedCostConfig = {
+ calculationMethod: 'DDP_PREFERRED',
+ endUse: 'NOT_FOR_RESALE',
+ tariffRate: 'ZONOS_PREFERRED'
+ };
+
+ const query = `mutation CalculateLandedCost($parties: [PartyCreateWorkflowInput!]!, $items: [ItemCreateWorkflowInput!]!, $shipmentRating: [ShipmentRatingCreateWorkflowInput!]!, $landedCostConfig: LandedCostWorkFlowInput!) {
+ partyCreateWorkflow(input: $parties) { id type }
+ itemCreateWorkflow(input: $items) { id amount }
+ shipmentRatingCreateWorkflow(input: $shipmentRating) { id amount }
+ landedCostCalculateWorkflow(input: $landedCostConfig) { id duties { amount currency } taxes { amount currency } fees { amount currency } }
+}`;
+
+ try {
+ const res = await fetch('https://api.zonos.com/graphql', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ credentialToken
+ },
+ body: JSON.stringify({
+ query,
+ variables: { parties, items, shipmentRating, landedCostConfig }
+ })
+ });
+
+ if (!res.ok) {
+ console.error('Zonos API error:', res.status, await res.text());
+ return null;
+ }
+
+ const result = await res.json();
+
+ if (result.errors) {
+ console.error('Zonos GraphQL errors:', result.errors);
+ return null;
+ }
+
+ const landedCost = result.data?.landedCostCalculateWorkflow;
+ if (!landedCost) return null;
+
+ const sumAmounts = (arr: Array<{ amount: number }> | undefined) =>
+ (arr || []).reduce((sum, entry) => sum + (entry.amount || 0), 0);
+
+ const duties = sumAmounts(landedCost.duties);
+ const taxes = sumAmounts(landedCost.taxes);
+ const fees = sumAmounts(landedCost.fees);
+
+ return {
+ duties: Math.round(duties * 100) / 100,
+ taxes: Math.round(taxes * 100) / 100,
+ fees: Math.round(fees * 100) / 100,
+ total: Math.round((duties + taxes + fees) * 100) / 100,
+ currency: 'USD'
+ };
+ } catch (err) {
+ console.error('Zonos API request failed:', err);
+ return null;
+ }
+}
+
+export async function fetchCheapestRate(params: {
+ country: string;
+ postalCode?: string;
+ province?: string;
+ weightGrams: number;
+ lengthIn: number;
+ widthIn: number;
+ heightIn: number;
+ packageType: string;
+}): Promise<{ serviceName: string; shippingCostUsd: number } | null> {
+ const originPostal = env.CP_ORIGIN_POSTAL_CODE;
+ if (!originPostal || !env.CP_API_USERNAME || !env.CP_API_PASSWORD || !env.CP_CUSTOMER_NUMBER) {
+ return null;
+ }
+
+ let effectiveLength = params.lengthIn;
+ let effectiveWidth = params.widthIn;
+ let effectivePackageType = params.packageType;
+ if (params.packageType === 'flat' || params.packageType === 'envelope') {
+ const l = Math.max(params.lengthIn, params.widthIn);
+ const w = Math.min(params.lengthIn, params.widthIn);
+ if (l <= 6 && w <= 4) { effectiveLength = 6; effectiveWidth = 4; }
+ else if (l <= 9 && w <= 6) { effectiveLength = 9; effectiveWidth = 6; }
+ else { effectiveLength = l; effectiveWidth = w; effectivePackageType = 'box'; }
+ }
+
+ const lengthCm = inchesToCm(effectiveLength);
+ const widthCm = inchesToCm(effectiveWidth);
+ const heightCm = effectivePackageType === 'box'
+ ? inchesToCm(params.packageType === 'box' ? params.heightIn : 0.5)
+ : 0.5;
+
+ const allOptions: Array<{ serviceName: string; total: number }> = [];
+
+ const lettermailOpts = getLetterMailOptions(params.weightGrams, lengthCm, widthCm, heightCm, params.country);
+ for (const opt of lettermailOpts) {
+ allOptions.push({ serviceName: opt.serviceName, total: opt.priceDetails.total });
+ }
+
+ try {
+ const parcelRates = await fetchRates({
+ country: params.country,
+ postalCode: params.postalCode,
+ weightKg: params.weightGrams * GRAMS_TO_KG,
+ lengthCm,
+ widthCm,
+ heightCm
+ });
+ for (const rate of parcelRates) {
+ allOptions.push({ serviceName: rate.serviceName, total: rate.priceDetails.total });
+ }
+ } catch (err) {
+ console.error('Parcel rate lookup failed:', err);
+ }
+
+ try {
+ if (env.CHITCHATS_ACCESS_TOKEN && env.CHITCHATS_CLIENT_ID) {
+ const chitChatsRates = await fetchChitChatsRates({
+ country: params.country,
+ postalCode: params.postalCode,
+ province: params.province,
+ name: 'Rate Quote',
+ address1: '123 Main St',
+ city: 'Unknown',
+ weightGrams: params.weightGrams,
+ lengthIn: effectiveLength,
+ widthIn: effectiveWidth,
+ heightIn: params.packageType === 'box' ? params.heightIn : 0.5,
+ valueCad: 1.00
+ });
+ for (const rate of chitChatsRates) {
+ allOptions.push({ serviceName: rate.serviceName, total: rate.priceDetails.total });
+ }
+ }
+ } catch (err) {
+ console.error('Chit Chats rate lookup failed:', err);
+ }
+
+ if (allOptions.length === 0) return null;
+
+ allOptions.sort((a, b) => a.total - b.total);
+ return { serviceName: allOptions[0].serviceName, shippingCostUsd: allOptions[0].total };
+}
diff --git a/resolution-frontend/src/lib/server/chit-chats.ts b/resolution-frontend/src/lib/server/chit-chats.ts
new file mode 100644
index 0000000..5af5545
--- /dev/null
+++ b/resolution-frontend/src/lib/server/chit-chats.ts
@@ -0,0 +1,292 @@
+import { env } from '$env/dynamic/private';
+import type { RateOption } from './canada-post';
+import { getCadToUsdRate } from './exchange-rate';
+import { arrayBufferToBase64 } from './utils';
+
+function formatHsCode(code: string | null | undefined): string {
+ if (!code) return '7117199000';
+ const digits = code.replace(/[^0-9]/g, '');
+ if (digits.length === 0) return '7117199000';
+ if (digits.length > 10) return digits.substring(0, 10);
+ return digits;
+}
+
+
+export function getChitChatsConfig(): { baseUrl: string; accessToken: string; clientId: string } {
+ const accessToken = env.CHITCHATS_ACCESS_TOKEN;
+ const clientId = env.CHITCHATS_CLIENT_ID;
+
+ if (!accessToken || !clientId) {
+ throw new Error('Chit Chats not configured: CHITCHATS_ACCESS_TOKEN and CHITCHATS_CLIENT_ID required');
+ }
+
+ return {
+ baseUrl: `https://chitchats.com/api/v1/clients/${clientId}`,
+ accessToken,
+ clientId
+ };
+}
+
+export async function createChitChatsShipment(params: {
+ order: any;
+ weightGrams: number;
+ lengthIn: number;
+ widthIn: number;
+ heightIn: number;
+}): Promise<{ trackingNumber: string | null; labelBase64: string | null; shipmentId: string }> {
+ const { baseUrl, accessToken } = getChitChatsConfig();
+ const { order, weightGrams, lengthIn, widthIn, heightIn } = params;
+
+ const items = (order.items || []).filter((oi: any) => oi.warehouseItem);
+ const totalValue = items.length > 0
+ ? items.reduce((sum: number, oi: any) => sum + (oi.warehouseItem.costCents / 100) * oi.quantity, 0).toFixed(2)
+ : '1.00';
+
+ const isThickEnvelope = lengthIn <= 15 && widthIn <= 10 && heightIn <= 1;
+ const packageType = isThickEnvelope ? 'thick_envelope' : 'parcel';
+
+ const shipmentBody: Record = {
+ name: `${order.firstName} ${order.lastName}`,
+ address_1: order.addressLine1,
+ address_2: order.addressLine2 || undefined,
+ city: order.city,
+ province_code: order.stateProvince,
+ postal_code: order.postalCode || '',
+ country_code: order.country,
+ email: order.email || undefined,
+ phone: order.phone || undefined,
+ package_contents: 'merchandise',
+ value: totalValue,
+ value_currency: 'cad',
+ package_type: packageType,
+ weight_unit: 'g',
+ weight: weightGrams,
+ size_unit: 'in',
+ size_x: lengthIn,
+ size_y: widthIn,
+ size_z: heightIn,
+ postage_type: 'unknown',
+ ship_date: 'today',
+ line_items: items.map((oi: any) => ({
+ quantity: oi.quantity,
+ description: oi.warehouseItem.name,
+ value_amount: (oi.warehouseItem.costCents / 100).toFixed(2),
+ currency_code: 'cad',
+ origin_country: 'CA',
+ hs_tariff_code: formatHsCode(oi.warehouseItem.hsCode),
+ weight: oi.warehouseItem.weightGrams * oi.quantity,
+ weight_unit: 'g',
+ manufacturer_id: 'HACKCLUB',
+ manufacturer_contact: env.CP_SENDER_NAME || 'Hack Club',
+ manufacturer_street: env.CP_SENDER_ADDRESS || '',
+ manufacturer_city: env.CP_SENDER_CITY || '',
+ manufacturer_postal_code: (env.CP_ORIGIN_POSTAL_CODE || '').replace(/\s/g, ''),
+ manufacturer_province_code: env.CP_SENDER_PROVINCE || '',
+ manufacturer_country_code: 'CA'
+ }))
+ };
+
+ // Create shipment
+ const createRes = await fetch(`${baseUrl}/shipments`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: accessToken
+ },
+ body: JSON.stringify(shipmentBody)
+ });
+
+ if (!createRes.ok) {
+ const errText = await createRes.text();
+ console.error('Chit Chats Create Shipment error:', errText);
+ throw new Error(`Chit Chats shipment creation failed: ${createRes.status}`);
+ }
+
+ const createData = await createRes.json();
+ const shipment = createData.shipment || createData;
+ const shipmentId = shipment.id;
+
+ // Find cheapest rate
+ const rates = shipment.rates || [];
+ if (rates.length === 0) {
+ throw new Error('No rates returned from Chit Chats');
+ }
+
+ const cheapestRate = rates.reduce((cheapest: any, rate: any) => {
+ return parseFloat(rate.payment_amount) < parseFloat(cheapest.payment_amount) ? rate : cheapest;
+ }, rates[0]);
+
+ // Buy postage
+ const buyRes = await fetch(`${baseUrl}/shipments/${shipmentId}/buy`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: accessToken
+ },
+ body: JSON.stringify({ postage_type: cheapestRate.postage_type })
+ });
+
+ if (!buyRes.ok) {
+ const errText = await buyRes.text();
+ console.error('Chit Chats Buy Postage error:', errText);
+ throw new Error(`Chit Chats postage purchase failed: ${buyRes.status}`);
+ }
+
+ // Poll for ready status
+ let finalShipment: any = null;
+ for (let i = 0; i < 10; i++) {
+ await new Promise((r) => setTimeout(r, 1000));
+
+ const pollRes = await fetch(`${baseUrl}/shipments/${shipmentId}`, {
+ headers: { Authorization: accessToken }
+ });
+
+ if (!pollRes.ok) {
+ console.error('Chit Chats poll error:', pollRes.status);
+ continue;
+ }
+
+ const pollData = await pollRes.json();
+ finalShipment = pollData.shipment;
+
+ if (finalShipment.status === 'ready' || finalShipment.status === 'postage_purchase_failed') {
+ break;
+ }
+ }
+
+ if (!finalShipment || finalShipment.status === 'postage_purchase_failed') {
+ throw new Error('Chit Chats postage purchase failed');
+ }
+
+ // Fetch label PNG (4x6 format)
+ let labelBase64: string | null = null;
+ if (finalShipment.postage_label_png_url) {
+ try {
+ const labelRes = await fetch(finalShipment.postage_label_png_url);
+ if (labelRes.ok) {
+ const labelBuffer = await labelRes.arrayBuffer();
+ labelBase64 = `data:image/png;base64,${arrayBufferToBase64(labelBuffer)}`;
+ } else {
+ console.error('Chit Chats label fetch failed:', labelRes.status);
+ }
+ } catch (err) {
+ console.error('Chit Chats label fetch error:', err);
+ }
+ }
+
+ return {
+ trackingNumber: finalShipment.carrier_tracking_code || null,
+ labelBase64,
+ shipmentId: String(shipmentId)
+ };
+}
+
+export async function fetchChitChatsRates(params: {
+ country: string;
+ postalCode?: string;
+ province?: string;
+ name: string;
+ address1: string;
+ city: string;
+ weightGrams: number;
+ lengthIn: number;
+ widthIn: number;
+ heightIn: number;
+ valueCad: number;
+}): Promise {
+ const { baseUrl, accessToken } = getChitChatsConfig();
+
+ const isThickEnvelope = params.lengthIn <= 15 && params.widthIn <= 10 && params.heightIn <= 1;
+ const packageType = isThickEnvelope ? 'thick_envelope' : 'parcel';
+
+ const shipmentBody: Record = {
+ name: params.name,
+ address_1: params.address1,
+ city: params.city,
+ province_code: params.province || '',
+ postal_code: params.postalCode || '',
+ country_code: params.country,
+ package_contents: 'merchandise',
+ value: params.valueCad.toFixed(2),
+ value_currency: 'cad',
+ package_type: packageType,
+ weight_unit: 'g',
+ weight: params.weightGrams,
+ size_unit: 'in',
+ size_x: params.lengthIn,
+ size_y: params.widthIn,
+ size_z: params.heightIn,
+ postage_type: 'unknown',
+ ship_date: 'today',
+ description: 'Merchandise',
+ line_items: [{
+ quantity: 1,
+ description: 'Merchandise',
+ value_amount: params.valueCad.toFixed(2),
+ currency_code: 'cad',
+ origin_country: 'CA',
+ hs_tariff_code: '7117199000',
+ weight: params.weightGrams,
+ weight_unit: 'g',
+ manufacturer_id: 'HACKCLUB',
+ manufacturer_contact: env.CP_SENDER_NAME || 'Hack Club',
+ manufacturer_street: env.CP_SENDER_ADDRESS || '',
+ manufacturer_city: env.CP_SENDER_CITY || '',
+ manufacturer_postal_code: (env.CP_ORIGIN_POSTAL_CODE || '').replace(/\s/g, ''),
+ manufacturer_province_code: env.CP_SENDER_PROVINCE || '',
+ manufacturer_country_code: 'CA'
+ }]
+ };
+
+ const createRes = await fetch(`${baseUrl}/shipments`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: accessToken
+ },
+ body: JSON.stringify(shipmentBody)
+ });
+
+ if (!createRes.ok) {
+ const errText = await createRes.text();
+ console.error('Chit Chats rate fetch error:', errText);
+ throw new Error(`Chit Chats rate fetch failed: ${createRes.status}`);
+ }
+
+ const createData = await createRes.json();
+ const shipment = createData.shipment || createData;
+ const shipmentId = shipment.id;
+ const rates = shipment.rates || [];
+
+ const cadToUsd = await getCadToUsdRate();
+
+ const rateOptions: RateOption[] = rates.map((rate: any) => {
+ const transitMatch = (rate.delivery_time_description || '').match(/(\d+)/);
+ return {
+ serviceName: rate.postage_description || rate.postage_type,
+ serviceCode: `CHITCHATS.${rate.postage_type}`,
+ priceDetails: {
+ base: parseFloat(rate.purchase_amount || '0') * cadToUsd,
+ gst: parseFloat(rate.federal_tax || '0') * cadToUsd,
+ pst: parseFloat(rate.provincial_tax || '0') * cadToUsd,
+ hst: 0,
+ total: parseFloat(rate.payment_amount || '0') * cadToUsd
+ },
+ deliveryDate: 'N/A',
+ transitDays: transitMatch ? transitMatch[0] : 'N/A',
+ currency: 'USD'
+ };
+ });
+
+ // Delete temporary shipment
+ try {
+ await fetch(`${baseUrl}/shipments/${shipmentId}`, {
+ method: 'DELETE',
+ headers: { Authorization: accessToken }
+ });
+ } catch (err) {
+ console.error('Chit Chats cleanup error:', err);
+ }
+
+ return rateOptions;
+}
diff --git a/resolution-frontend/src/lib/server/countries.ts b/resolution-frontend/src/lib/server/countries.ts
new file mode 100644
index 0000000..9366839
--- /dev/null
+++ b/resolution-frontend/src/lib/server/countries.ts
@@ -0,0 +1,98 @@
+const COUNTRY_NAME_TO_CODE: Record = {
+ 'AFGHANISTAN': 'AF', 'ALBANIA': 'AL', 'ALGERIA': 'DZ', 'ANDORRA': 'AD', 'ANGOLA': 'AO',
+ 'ANTIGUA AND BARBUDA': 'AG', 'ARGENTINA': 'AR', 'ARMENIA': 'AM', 'AUSTRALIA': 'AU',
+ 'AUSTRIA': 'AT', 'AZERBAIJAN': 'AZ', 'BAHAMAS': 'BS', 'BAHRAIN': 'BH', 'BANGLADESH': 'BD',
+ 'BARBADOS': 'BB', 'BELARUS': 'BY', 'BELGIUM': 'BE', 'BELIZE': 'BZ', 'BENIN': 'BJ',
+ 'BHUTAN': 'BT', 'BOLIVIA': 'BO', 'BOSNIA AND HERZEGOVINA': 'BA', 'BOTSWANA': 'BW',
+ 'BRAZIL': 'BR', 'BRUNEI': 'BN', 'BULGARIA': 'BG', 'BURKINA FASO': 'BF', 'BURUNDI': 'BI',
+ 'CABO VERDE': 'CV', 'CAMBODIA': 'KH', 'CAMEROON': 'CM', 'CANADA': 'CA',
+ 'CENTRAL AFRICAN REPUBLIC': 'CF', 'CHAD': 'TD', 'CHILE': 'CL', 'CHINA': 'CN',
+ 'COLOMBIA': 'CO', 'COMOROS': 'KM', 'CONGO': 'CG',
+ 'DEMOCRATIC REPUBLIC OF THE CONGO': 'CD', 'COSTA RICA': 'CR', 'CROATIA': 'HR', 'CUBA': 'CU',
+ 'CYPRUS': 'CY', 'CZECH REPUBLIC': 'CZ', 'CZECHIA': 'CZ', 'DENMARK': 'DK', 'DJIBOUTI': 'DJ',
+ 'DOMINICA': 'DM', 'DOMINICAN REPUBLIC': 'DO', 'ECUADOR': 'EC', 'EGYPT': 'EG',
+ 'EL SALVADOR': 'SV', 'EQUATORIAL GUINEA': 'GQ', 'ERITREA': 'ER', 'ESTONIA': 'EE',
+ 'ESWATINI': 'SZ', 'ETHIOPIA': 'ET', 'FIJI': 'FJ', 'FINLAND': 'FI', 'FRANCE': 'FR',
+ 'GABON': 'GA', 'GAMBIA': 'GM', 'GEORGIA': 'GE', 'GERMANY': 'DE', 'GHANA': 'GH',
+ 'GREECE': 'GR', 'GRENADA': 'GD', 'GUATEMALA': 'GT', 'GUINEA': 'GN', 'GUINEA-BISSAU': 'GW',
+ 'GUYANA': 'GY', 'HAITI': 'HT', 'HONDURAS': 'HN', 'HUNGARY': 'HU', 'ICELAND': 'IS',
+ 'INDIA': 'IN', 'INDONESIA': 'ID', 'IRAN': 'IR', 'IRAQ': 'IQ', 'IRELAND': 'IE',
+ 'ISRAEL': 'IL', 'ITALY': 'IT', 'IVORY COAST': 'CI', "COTE D'IVOIRE": 'CI',
+ 'JAMAICA': 'JM', 'JAPAN': 'JP', 'JORDAN': 'JO', 'KAZAKHSTAN': 'KZ', 'KENYA': 'KE',
+ 'KIRIBATI': 'KI', 'SOUTH KOREA': 'KR', 'KOREA': 'KR', 'KUWAIT': 'KW', 'KYRGYZSTAN': 'KG',
+ 'LAOS': 'LA', 'LATVIA': 'LV', 'LEBANON': 'LB', 'LESOTHO': 'LS', 'LIBERIA': 'LR',
+ 'LIBYA': 'LY', 'LIECHTENSTEIN': 'LI', 'LITHUANIA': 'LT', 'LUXEMBOURG': 'LU',
+ 'MADAGASCAR': 'MG', 'MALAWI': 'MW', 'MALAYSIA': 'MY', 'MALDIVES': 'MV', 'MALI': 'ML',
+ 'MALTA': 'MT', 'MARSHALL ISLANDS': 'MH', 'MAURITANIA': 'MR', 'MAURITIUS': 'MU',
+ 'MEXICO': 'MX', 'MÉXICO': 'MX', 'MICRONESIA': 'FM', 'MOLDOVA': 'MD', 'MONACO': 'MC',
+ 'MONGOLIA': 'MN', 'MONTENEGRO': 'ME', 'MOROCCO': 'MA', 'MOZAMBIQUE': 'MZ', 'MYANMAR': 'MM',
+ 'NAMIBIA': 'NA', 'NAURU': 'NR', 'NEPAL': 'NP', 'NETHERLANDS': 'NL', 'NEW ZEALAND': 'NZ',
+ 'NICARAGUA': 'NI', 'NIGER': 'NE', 'NIGERIA': 'NG', 'NORTH MACEDONIA': 'MK',
+ 'NORWAY': 'NO', 'OMAN': 'OM', 'PAKISTAN': 'PK', 'PALAU': 'PW', 'PALESTINE': 'PS',
+ 'PANAMA': 'PA', 'PAPUA NEW GUINEA': 'PG', 'PARAGUAY': 'PY', 'PERU': 'PE',
+ 'PHILIPPINES': 'PH', 'POLAND': 'PL', 'PORTUGAL': 'PT', 'QATAR': 'QA', 'ROMANIA': 'RO',
+ 'RUSSIA': 'RU', 'RUSSIAN FEDERATION': 'RU', 'RWANDA': 'RW',
+ 'SAINT KITTS AND NEVIS': 'KN', 'SAINT LUCIA': 'LC', 'SAINT VINCENT AND THE GRENADINES': 'VC',
+ 'SAMOA': 'WS', 'SAN MARINO': 'SM', 'SAO TOME AND PRINCIPE': 'ST', 'SAUDI ARABIA': 'SA',
+ 'SENEGAL': 'SN', 'SERBIA': 'RS', 'SEYCHELLES': 'SC', 'SIERRA LEONE': 'SL',
+ 'SINGAPORE': 'SG', 'SLOVAKIA': 'SK', 'SLOVENIA': 'SI', 'SOLOMON ISLANDS': 'SB',
+ 'SOMALIA': 'SO', 'SOUTH AFRICA': 'ZA', 'SOUTH SUDAN': 'SS', 'SPAIN': 'ES',
+ 'SRI LANKA': 'LK', 'SUDAN': 'SD', 'SURINAME': 'SR', 'SWEDEN': 'SE', 'SWITZERLAND': 'CH',
+ 'SYRIA': 'SY', 'TAIWAN': 'TW', 'TAJIKISTAN': 'TJ', 'TANZANIA': 'TZ', 'THAILAND': 'TH',
+ 'TIMOR-LESTE': 'TL', 'TOGO': 'TG', 'TONGA': 'TO', 'TRINIDAD AND TOBAGO': 'TT',
+ 'TUNISIA': 'TN', 'TURKEY': 'TR', 'TÜRKIYE': 'TR', 'TURKMENISTAN': 'TM', 'TUVALU': 'TV',
+ 'UGANDA': 'UG', 'UKRAINE': 'UA', 'UNITED ARAB EMIRATES': 'AE', 'UAE': 'AE',
+ 'UNITED KINGDOM': 'GB', 'UK': 'GB', 'GREAT BRITAIN': 'GB', 'ENGLAND': 'GB',
+ 'UNITED STATES': 'US', 'UNITED STATES OF AMERICA': 'US', 'USA': 'US',
+ 'URUGUAY': 'UY', 'UZBEKISTAN': 'UZ', 'VANUATU': 'VU', 'VATICAN CITY': 'VA',
+ 'VENEZUELA': 'VE', 'VIETNAM': 'VN', 'VIET NAM': 'VN', 'YEMEN': 'YE', 'ZAMBIA': 'ZM',
+ 'ZIMBABWE': 'ZW',
+ 'HONG KONG': 'HK', 'MACAU': 'MO', 'PUERTO RICO': 'PR', 'GUAM': 'GU',
+ 'US VIRGIN ISLANDS': 'VI', 'AMERICAN SAMOA': 'AS',
+};
+
+/**
+ * Resolves a country value to an ISO 3166-1 alpha-2 code.
+ * Accepts either a 2-letter code (passed through) or a full country name.
+ * Returns the input uppercased if no mapping is found.
+ */
+export function resolveCountryCode(country: string): string {
+ const upper = country.trim().toUpperCase();
+ if (upper.length === 2) return upper;
+ return COUNTRY_NAME_TO_CODE[upper] ?? upper;
+}
+
+const US_STATE_TO_CODE: Record = {
+ 'ALABAMA': 'AL', 'ALASKA': 'AK', 'ARIZONA': 'AZ', 'ARKANSAS': 'AR', 'CALIFORNIA': 'CA',
+ 'COLORADO': 'CO', 'CONNECTICUT': 'CT', 'DELAWARE': 'DE', 'FLORIDA': 'FL', 'GEORGIA': 'GA',
+ 'HAWAII': 'HI', 'IDAHO': 'ID', 'ILLINOIS': 'IL', 'INDIANA': 'IN', 'IOWA': 'IA',
+ 'KANSAS': 'KS', 'KENTUCKY': 'KY', 'LOUISIANA': 'LA', 'MAINE': 'ME', 'MARYLAND': 'MD',
+ 'MASSACHUSETTS': 'MA', 'MICHIGAN': 'MI', 'MINNESOTA': 'MN', 'MISSISSIPPI': 'MS',
+ 'MISSOURI': 'MO', 'MONTANA': 'MT', 'NEBRASKA': 'NE', 'NEVADA': 'NV', 'NEW HAMPSHIRE': 'NH',
+ 'NEW JERSEY': 'NJ', 'NEW MEXICO': 'NM', 'NEW YORK': 'NY', 'NORTH CAROLINA': 'NC',
+ 'NORTH DAKOTA': 'ND', 'OHIO': 'OH', 'OKLAHOMA': 'OK', 'OREGON': 'OR', 'PENNSYLVANIA': 'PA',
+ 'RHODE ISLAND': 'RI', 'SOUTH CAROLINA': 'SC', 'SOUTH DAKOTA': 'SD', 'TENNESSEE': 'TN',
+ 'TEXAS': 'TX', 'UTAH': 'UT', 'VERMONT': 'VT', 'VIRGINIA': 'VA', 'WASHINGTON': 'WA',
+ 'WEST VIRGINIA': 'WV', 'WISCONSIN': 'WI', 'WYOMING': 'WY',
+ 'DISTRICT OF COLUMBIA': 'DC', 'PUERTO RICO': 'PR', 'GUAM': 'GU',
+ 'AMERICAN SAMOA': 'AS', 'US VIRGIN ISLANDS': 'VI',
+};
+
+const CA_PROVINCE_TO_CODE: Record = {
+ 'ALBERTA': 'AB', 'BRITISH COLUMBIA': 'BC', 'MANITOBA': 'MB', 'NEW BRUNSWICK': 'NB',
+ 'NEWFOUNDLAND AND LABRADOR': 'NL', 'NEWFOUNDLAND': 'NL', 'NOVA SCOTIA': 'NS',
+ 'NORTHWEST TERRITORIES': 'NT', 'NUNAVUT': 'NU', 'ONTARIO': 'ON',
+ 'PRINCE EDWARD ISLAND': 'PE', 'QUEBEC': 'QC', 'QUÉBEC': 'QC',
+ 'SASKATCHEWAN': 'SK', 'YUKON': 'YT',
+};
+
+/**
+ * Resolves a state/province value to a short code.
+ * Accepts a 2-letter code (passed through) or full name for US states / CA provinces.
+ */
+export function resolveStateCode(state: string, country?: string): string {
+ const trimmed = state.trim();
+ if (trimmed.length <= 2) return trimmed.toUpperCase();
+ const upper = trimmed.toUpperCase();
+ return US_STATE_TO_CODE[upper] ?? CA_PROVINCE_TO_CODE[upper] ?? trimmed;
+}
diff --git a/resolution-frontend/src/lib/server/db/schema.ts b/resolution-frontend/src/lib/server/db/schema.ts
index c31c9b9..bb5a87e 100644
--- a/resolution-frontend/src/lib/server/db/schema.ts
+++ b/resolution-frontend/src/lib/server/db/schema.ts
@@ -8,7 +8,9 @@ export const enrollmentStatusEnum = pgEnum('enrollment_status', ['ACTIVE', 'DROP
export const pathwayEnum = pgEnum('pathway', ['PYTHON', 'RUST', 'GAME_DEV', 'HARDWARE', 'DESIGN', 'GENERAL_CODING']);
export const difficultyEnum = pgEnum('difficulty', ['BEGINNER', 'INTERMEDIATE', 'ADVANCED']);
export const shipStatusEnum = pgEnum('ship_status', ['PLANNED', 'IN_PROGRESS', 'SHIPPED', 'MISSED']);
-export const payoutStatusEnum = pgEnum('payout_status', ['DRAFT', 'PENDING', 'PAID', 'CANCELED']);
+export const payoutStatusEnum = pgEnum('payout_status', ['DRAFT', 'PENDING', 'PAID', 'CANCELLED']);
+export const warehouseOrderStatusEnum = pgEnum('warehouse_order_status', ['DRAFT', 'ESTIMATED', 'APPROVED', 'SHIPPED', 'CANCELLED']);
+export const warehouseBatchStatusEnum = pgEnum('warehouse_batch_status', ['AWAITING_MAPPING', 'MAPPED', 'PROCESSED']);
// Tables
export const user = pgTable('user', {
@@ -149,6 +151,7 @@ export const userRelations = relations(user, ({ many }) => ({
weeklyShips: many(weeklyShip),
payouts: many(ambassadorPayout),
referralLinks: many(referralLink),
+ warehouseOrders: many(warehouseOrder),
reviewerAssignments: many(reviewerPathway)
}));
@@ -288,3 +291,162 @@ export const referralSignupRelations = relations(referralSignup, ({ one }) => ({
referralLink: one(referralLink, { fields: [referralSignup.referralLinkId], references: [referralLink.id] }),
user: one(user, { fields: [referralSignup.userId], references: [user.id] })
}));
+
+// Warehouse categories
+export const warehouseCategory = pgTable('warehouse_category', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ name: text('name').notNull(),
+ sortOrder: integer('sort_order').notNull().default(0),
+ createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow()
+});
+
+// Warehouse items - inventory managed by admins
+export const warehouseItem = pgTable('warehouse_item', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ categoryId: text('category_id').references(() => warehouseCategory.id, { onDelete: 'set null' }),
+ name: text('name').notNull(),
+ sku: text('sku').notNull().unique(),
+ sizing: text('sizing'),
+ packageType: text('package_type').notNull().default('box'),
+ lengthIn: real('length_in').notNull(),
+ widthIn: real('width_in').notNull(),
+ heightIn: real('height_in').notNull(),
+ weightGrams: real('weight_grams').notNull(),
+ costCents: integer('cost_cents').notNull(),
+ hsCode: text('hs_code').notNull().default(''),
+ quantity: integer('quantity').notNull().default(0),
+ imageUrl: text('image_url'),
+ createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(),
+ updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow()
+});
+
+// Warehouse orders
+export const warehouseOrder = pgTable('warehouse_order', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ fulfillmentId: integer('fulfillment_id').generatedAlwaysAsIdentity().unique(),
+ createdById: text('created_by_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
+ batchId: text('batch_id'),
+ status: warehouseOrderStatusEnum('status').notNull().default('DRAFT'),
+ firstName: text('first_name').notNull(),
+ lastName: text('last_name').notNull(),
+ email: text('email').notNull(),
+ phone: text('phone'),
+ addressLine1: text('address_line_1').notNull(),
+ addressLine2: text('address_line_2'),
+ city: text('city').notNull(),
+ stateProvince: text('state_province').notNull(),
+ postalCode: text('postal_code'),
+ country: text('country').notNull(),
+ estimatedShippingCents: integer('estimated_shipping_cents'),
+ estimatedDutiesCents: integer('estimated_duties_cents'),
+ estimatedServiceName: text('estimated_service_name'),
+ estimatedServiceCode: text('estimated_service_code'),
+ estimatedPackageType: text('estimated_package_type'),
+ estimatedTotalLengthIn: real('estimated_total_length_in'),
+ estimatedTotalWidthIn: real('estimated_total_width_in'),
+ estimatedTotalHeightIn: real('estimated_total_height_in'),
+ estimatedTotalWeightGrams: real('estimated_total_weight_grams'),
+ trackingNumber: text('tracking_number'),
+ labelUrl: text('label_url'),
+ // Nullable because DRAFT orders don't have a shipping method yet; set when label is created
+ shippingMethod: text('shipping_method'), // 'canada_post', 'lettermail', or 'chitchats'
+ notes: text('notes'),
+ createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(),
+ updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow()
+});
+
+// Warehouse order line items
+export const warehouseOrderItem = pgTable('warehouse_order_item', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ orderId: text('order_id').notNull().references(() => warehouseOrder.id, { onDelete: 'cascade' }),
+ warehouseItemId: text('warehouse_item_id').notNull().references(() => warehouseItem.id, { onDelete: 'restrict' }),
+ quantity: integer('quantity').notNull().default(1),
+ // Selected size variant (e.g. 'S', 'M', 'L') when the warehouse item has sizing options
+ sizingChoice: text('sizing_choice')
+});
+
+// Warehouse order tags for filtering
+export const warehouseOrderTag = pgTable('warehouse_order_tag', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ orderId: text('order_id').notNull().references(() => warehouseOrder.id, { onDelete: 'cascade' }),
+ tag: text('tag').notNull()
+}, (table) => [
+ uniqueIndex('warehouse_order_tag_unique_idx').on(table.orderId, table.tag),
+ index('warehouse_order_tag_tag_idx').on(table.tag)
+]);
+
+export const warehouseOrderRelations = relations(warehouseOrder, ({ one, many }) => ({
+ createdBy: one(user, { fields: [warehouseOrder.createdById], references: [user.id] }),
+ items: many(warehouseOrderItem),
+ tags: many(warehouseOrderTag)
+}));
+
+export const warehouseOrderItemRelations = relations(warehouseOrderItem, ({ one }) => ({
+ order: one(warehouseOrder, { fields: [warehouseOrderItem.orderId], references: [warehouseOrder.id] }),
+ warehouseItem: one(warehouseItem, { fields: [warehouseOrderItem.warehouseItemId], references: [warehouseItem.id] })
+}));
+
+export const warehouseOrderTagRelations = relations(warehouseOrderTag, ({ one }) => ({
+ order: one(warehouseOrder, { fields: [warehouseOrderTag.orderId], references: [warehouseOrder.id] })
+}));
+
+// Order templates — reusable item lists for quickly creating warehouse orders (e.g. "Sticker Pack")
+// isPublic controls whether other ambassadors can see and use this template
+export const warehouseOrderTemplate = pgTable('warehouse_order_template', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ createdById: text('created_by_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
+ name: text('name').notNull(),
+ isPublic: boolean('is_public').notNull().default(false),
+ createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(),
+ updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow()
+});
+
+export const warehouseOrderTemplateItem = pgTable('warehouse_order_template_item', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ templateId: text('template_id').notNull().references(() => warehouseOrderTemplate.id, { onDelete: 'cascade' }),
+ warehouseItemId: text('warehouse_item_id').notNull().references(() => warehouseItem.id, { onDelete: 'restrict' }),
+ quantity: integer('quantity').notNull().default(1)
+});
+
+export const warehouseOrderTemplateRelations = relations(warehouseOrderTemplate, ({ one, many }) => ({
+ createdBy: one(user, { fields: [warehouseOrderTemplate.createdById], references: [user.id] }),
+ items: many(warehouseOrderTemplateItem)
+}));
+
+export const warehouseOrderTemplateItemRelations = relations(warehouseOrderTemplateItem, ({ one }) => ({
+ template: one(warehouseOrderTemplate, { fields: [warehouseOrderTemplateItem.templateId], references: [warehouseOrderTemplate.id] }),
+ warehouseItem: one(warehouseItem, { fields: [warehouseOrderTemplateItem.warehouseItemId], references: [warehouseItem.id] })
+}));
+
+// Batches
+export const warehouseBatch = pgTable('warehouse_batch', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ createdById: text('created_by_id').notNull().references(() => user.id, { onDelete: 'cascade' }),
+ templateId: text('template_id').notNull().references(() => warehouseOrderTemplate.id, { onDelete: 'restrict' }),
+ title: text('title'),
+ status: warehouseBatchStatusEnum('status').notNull().default('AWAITING_MAPPING'),
+ csvData: text('csv_data').notNull(),
+ fieldMapping: text('field_mapping'),
+ addressCount: integer('address_count').notNull().default(0),
+ createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(),
+ updatedAt: timestamp('updated_at', { mode: 'date' }).notNull().defaultNow()
+});
+
+export const warehouseBatchTag = pgTable('warehouse_batch_tag', {
+ id: text('id').primaryKey().$defaultFn(() => createId()),
+ batchId: text('batch_id').notNull().references(() => warehouseBatch.id, { onDelete: 'cascade' }),
+ tag: text('tag').notNull()
+}, (table) => [
+ uniqueIndex('warehouse_batch_tag_unique_idx').on(table.batchId, table.tag)
+]);
+
+export const warehouseBatchRelations = relations(warehouseBatch, ({ one, many }) => ({
+ createdBy: one(user, { fields: [warehouseBatch.createdById], references: [user.id] }),
+ template: one(warehouseOrderTemplate, { fields: [warehouseBatch.templateId], references: [warehouseOrderTemplate.id] }),
+ tags: many(warehouseBatchTag)
+}));
+
+export const warehouseBatchTagRelations = relations(warehouseBatchTag, ({ one }) => ({
+ batch: one(warehouseBatch, { fields: [warehouseBatchTag.batchId], references: [warehouseBatch.id] })
+}));
+
diff --git a/resolution-frontend/src/lib/server/exchange-rate.ts b/resolution-frontend/src/lib/server/exchange-rate.ts
new file mode 100644
index 0000000..b8a9152
--- /dev/null
+++ b/resolution-frontend/src/lib/server/exchange-rate.ts
@@ -0,0 +1,43 @@
+import { env } from '$env/dynamic/private';
+
+const CACHE_DURATION_MS = 60 * 60 * 1000; // 1 hour
+
+let cachedRate: number | null = null;
+let cachedAt = 0;
+
+const FALLBACK_CAD_TO_USD = 0.73;
+
+/**
+ * Returns the current CAD to USD exchange rate.
+ * Fetches from an external API and caches for 1 hour.
+ * Falls back to a static rate if the API is unavailable.
+ */
+export async function getCadToUsdRate(): Promise {
+ if (cachedRate && Date.now() - cachedAt < CACHE_DURATION_MS) {
+ return cachedRate;
+ }
+
+ try {
+ const res = await fetch('https://open.er-api.com/v6/latest/CAD', {
+ signal: AbortSignal.timeout(5000)
+ });
+
+ if (res.ok) {
+ const data = await res.json();
+ const rate = data?.rates?.USD;
+ if (typeof rate === 'number' && rate > 0) {
+ cachedRate = Math.round(rate * 10000) / 10000;
+ cachedAt = Date.now();
+ return cachedRate;
+ }
+ }
+ } catch (err) {
+ console.error('Exchange rate fetch failed, using fallback:', err);
+ }
+
+ // Use env override or fallback
+ const envRate = env.CAD_TO_USD_RATE ? parseFloat(env.CAD_TO_USD_RATE) : null;
+ if (envRate && envRate > 0) return envRate;
+
+ return FALLBACK_CAD_TO_USD;
+}
diff --git a/resolution-frontend/src/lib/server/hcb.ts b/resolution-frontend/src/lib/server/hcb.ts
new file mode 100644
index 0000000..bc281a1
--- /dev/null
+++ b/resolution-frontend/src/lib/server/hcb.ts
@@ -0,0 +1,98 @@
+import { env } from '$env/dynamic/private';
+
+const HCB_BASE_URL = 'https://hcb.hackclub.com/api/v4';
+const HCB_TOKEN_URL = 'https://hcb.hackclub.com/api/v4/oauth/token';
+const DEFAULT_REFRESH_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes fallback
+
+const RESOLUTION_ORG_ID = 'org_0zuxO2';
+
+const PATHWAY_ORG_MAP: Record = {
+ PYTHON: 'org_lbu4GE',
+ GENERAL_CODING: 'org_5GuRj1',
+ GAME_DEV: 'org_NOuVO8',
+ DESIGN: 'org_Q4uqwO',
+ RUST: 'org_G3uEbg',
+ HARDWARE: 'org_0zuxZA'
+};
+
+let cachedAccessToken: string | null = null;
+let cachedRefreshToken: string | null = null;
+let tokenExpiresAt = 0;
+
+async function getAccessToken(): Promise {
+ if (cachedAccessToken && Date.now() < tokenExpiresAt) {
+ return cachedAccessToken;
+ }
+
+ const clientId = env.HCB_CLIENT_ID;
+ const clientSecret = env.HCB_CLIENT_SECRET;
+ const refreshToken = cachedRefreshToken || env.HCB_REFRESH_TOKEN;
+ if (!clientId || !clientSecret || !refreshToken) {
+ throw new Error('HCB not configured: HCB_CLIENT_ID, HCB_CLIENT_SECRET, and HCB_REFRESH_TOKEN required');
+ }
+
+ // Use initial access token on first call if not yet refreshed
+ if (!cachedAccessToken && env.HCB_ACCESS_TOKEN) {
+ cachedAccessToken = env.HCB_ACCESS_TOKEN;
+ cachedRefreshToken = refreshToken;
+ tokenExpiresAt = Date.now() + DEFAULT_REFRESH_INTERVAL_MS;
+ return cachedAccessToken;
+ }
+
+ const res = await fetch(HCB_TOKEN_URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: new URLSearchParams({
+ grant_type: 'refresh_token',
+ refresh_token: refreshToken,
+ client_id: clientId,
+ client_secret: clientSecret
+ })
+ });
+
+ if (!res.ok) {
+ const body = await res.text();
+ throw new Error(`HCB token refresh failed (${res.status}): ${body}`);
+ }
+
+ const data = await res.json();
+ cachedAccessToken = data.access_token;
+ cachedRefreshToken = data.refresh_token;
+ // Use expires_in from OAuth response if available, otherwise fall back to default
+ const expiresInMs = data.expires_in ? data.expires_in * 1000 : DEFAULT_REFRESH_INTERVAL_MS;
+ tokenExpiresAt = Date.now() + expiresInMs;
+
+ return cachedAccessToken!;
+}
+
+export async function createHcbTransfer(
+ fromOrgId: string,
+ amountCents: number,
+ memo: string
+): Promise<{ id: string; amount_cents: number; name: string }> {
+ const accessToken = await getAccessToken();
+
+ const res = await fetch(`${HCB_BASE_URL}/organizations/${fromOrgId}/transfers`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${accessToken}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ to_organization_id: RESOLUTION_ORG_ID,
+ amount_cents: amountCents,
+ name: memo
+ })
+ });
+
+ if (!res.ok) {
+ const body = await res.text();
+ throw new Error(`HCB transfer failed (${res.status}): ${body}`);
+ }
+
+ return res.json();
+}
+
+export function getOrgIdForPathway(pathway: string): string | null {
+ return PATHWAY_ORG_MAP[pathway] ?? null;
+}
diff --git a/resolution-frontend/src/lib/server/utils.ts b/resolution-frontend/src/lib/server/utils.ts
new file mode 100644
index 0000000..c1806d4
--- /dev/null
+++ b/resolution-frontend/src/lib/server/utils.ts
@@ -0,0 +1,4 @@
+/** Convert an ArrayBuffer or Uint8Array to a base64 string */
+export function arrayBufferToBase64(buffer: ArrayBuffer | Uint8Array): string {
+ return Buffer.from(buffer).toString('base64');
+}
diff --git a/resolution-frontend/src/lib/server/validation/schemas.test.ts b/resolution-frontend/src/lib/server/validation/schemas.test.ts
index 73e04a7..59ced9a 100644
--- a/resolution-frontend/src/lib/server/validation/schemas.test.ts
+++ b/resolution-frontend/src/lib/server/validation/schemas.test.ts
@@ -4,6 +4,7 @@ import {
markShippedSchema,
updateShipStatusSchema,
enrollSeasonSchema,
+ shippingRateSchema,
projectSubmissionSchema
} from './schemas';
@@ -109,6 +110,71 @@ describe('enrollSeasonSchema', () => {
});
});
+describe('shippingRateSchema', () => {
+ const validBox = {
+ country: 'US',
+ street: '123 Main St',
+ city: 'Springfield',
+ province: 'IL',
+ postalCode: '62701',
+ weight: 2.5,
+ packageType: 'box' as const,
+ length: 10,
+ width: 5,
+ height: 3
+ };
+
+ const validEnvelope = {
+ country: 'US',
+ street: '123 Main St',
+ city: 'Springfield',
+ province: 'IL',
+ weight: 0.5,
+ packageType: 'envelope' as const,
+ length: 10,
+ width: 5
+ };
+
+ it('accepts valid box input', () => {
+ expect(shippingRateSchema.safeParse(validBox).success).toBe(true);
+ });
+
+ it('accepts valid envelope input', () => {
+ expect(shippingRateSchema.safeParse(validEnvelope).success).toBe(true);
+ });
+
+ it('uppercases country code', () => {
+ const result = shippingRateSchema.safeParse({ ...validBox, country: 'us' });
+ expect(result.success).toBe(true);
+ if (result.success) {
+ expect(result.data.country).toBe('US');
+ }
+ });
+
+ it('rejects country code not exactly 2 chars', () => {
+ expect(shippingRateSchema.safeParse({ ...validBox, country: 'USA' }).success).toBe(false);
+ });
+
+ it('rejects non-positive weight', () => {
+ expect(shippingRateSchema.safeParse({ ...validBox, weight: 0 }).success).toBe(false);
+ expect(shippingRateSchema.safeParse({ ...validBox, weight: -1 }).success).toBe(false);
+ });
+
+ it('rejects box without height', () => {
+ const { height, ...noHeight } = validBox;
+ expect(shippingRateSchema.safeParse(noHeight).success).toBe(false);
+ });
+
+ it('rejects empty street', () => {
+ expect(shippingRateSchema.safeParse({ ...validBox, street: '' }).success).toBe(false);
+ });
+
+ it('allows optional postalCode', () => {
+ const { postalCode, ...noPostal } = validBox;
+ expect(shippingRateSchema.safeParse(noPostal).success).toBe(true);
+ });
+});
+
describe('projectSubmissionSchema', () => {
const valid = {
codeUrl: 'https://github.com/user/repo',
@@ -284,5 +350,3 @@ describe('projectSubmissionSchema', () => {
expect(projectSubmissionSchema.safeParse({ ...valid, addressLine2: 'a'.repeat(201) }).success).toBe(false);
});
});
-
-
diff --git a/resolution-frontend/src/lib/server/validation/schemas.ts b/resolution-frontend/src/lib/server/validation/schemas.ts
index f87bf23..1a655e7 100644
--- a/resolution-frontend/src/lib/server/validation/schemas.ts
+++ b/resolution-frontend/src/lib/server/validation/schemas.ts
@@ -36,6 +36,43 @@ export const workshopIdSchema = z.object({
workshopId: z.string().min(1, 'Workshop ID is required')
});
+const envelopeSchema = z.object({
+ packageType: z.literal('envelope'),
+ length: z.number().positive('Length must be positive'),
+ width: z.number().positive('Width must be positive')
+});
+
+const flatSchema = z.object({
+ packageType: z.literal('flat'),
+ length: z.number().positive('Length must be positive'),
+ width: z.number().positive('Width must be positive')
+});
+
+const boxSchema = z.object({
+ packageType: z.literal('box'),
+ length: z.number().positive('Length must be positive'),
+ width: z.number().positive('Width must be positive'),
+ height: z.number().positive('Height must be positive')
+});
+
+export const shippingRateSchema = z.object({
+ country: z.string().length(2, 'Country must be a 2-letter ISO code').toUpperCase(),
+ street: z.string().min(1, 'Street is required'),
+ city: z.string().min(1, 'City is required'),
+ province: z.string().min(1, 'Province/State is required'),
+ postalCode: z.string().optional(),
+ weight: z.number().positive('Weight must be positive'),
+ items: z.array(z.object({
+ name: z.string(),
+ sku: z.string().optional(),
+ hsCode: z.string().optional(),
+ costCents: z.number(),
+ quantity: z.number().int().positive()
+ })).optional()
+}).and(z.discriminatedUnion('packageType', [envelopeSchema, flatSchema, boxSchema]));
+
+export type ShippingRateInput = z.infer;
+
const safeUrl = z.string().url('Please enter a valid URL').max(2000).refine(
(val) => /^https?:\/\//i.test(val),
{ message: 'URL must use http or https' }
diff --git a/resolution-frontend/src/routes/api/fulfillment/get-label/+server.ts b/resolution-frontend/src/routes/api/fulfillment/get-label/+server.ts
new file mode 100644
index 0000000..7a6d7f7
--- /dev/null
+++ b/resolution-frontend/src/routes/api/fulfillment/get-label/+server.ts
@@ -0,0 +1,328 @@
+import { env } from '$env/dynamic/private';
+import { json, error } from '@sveltejs/kit';
+import { db } from '$lib/server/db';
+import { warehouseOrder, ambassadorPathway } from '$lib/server/db/schema';
+import { eq, and, ne } from 'drizzle-orm';
+import type { RequestHandler } from './$types';
+import { requireAuth } from '$lib/server/auth/guard';
+import { GRAMS_TO_KG, inchesToCm, isLettermail, getServiceCode, createShipment } from '$lib/server/canada-post';
+import { createChitChatsShipment } from '$lib/server/chit-chats';
+import { arrayBufferToBase64 } from '$lib/server/utils';
+
+function buildPackingSlipBase64(order: any): string {
+ const lines: string[] = [
+ `PACKING SLIP`,
+ `Order #${order.fulfillmentId}`,
+ `Date: ${new Date().toLocaleDateString('en-US')}`,
+ ``,
+ `SHIP TO:`,
+ `${order.firstName} ${order.lastName}`,
+ `${order.addressLine1}`,
+ order.addressLine2 || '',
+ `${order.city}, ${order.stateProvince} ${order.postalCode || ''}`,
+ `${order.country}`,
+ ``,
+ `CONTENTS:`,
+ `${'Item'.padEnd(35)} ${'Size'.padEnd(10)} ${'Qty'.padEnd(5)}`,
+ `${'─'.repeat(50)}`,
+ ];
+ for (const oi of order.items) {
+ const name = oi.warehouseItem.name.substring(0, 35).padEnd(35);
+ const size = (oi.sizingChoice || '—').padEnd(10);
+ const qty = String(oi.quantity).padEnd(5);
+ lines.push(`${name} ${size} ${qty}`);
+ }
+ lines.push(`${'─'.repeat(50)}`);
+ lines.push(`Total items: ${order.items.reduce((s: number, oi: any) => s + oi.quantity, 0)}`);
+ if (order.notes) {
+ lines.push(``);
+ lines.push(`NOTES: ${order.notes}`);
+ }
+ const text = lines.filter(l => l !== undefined).join('\n');
+ return btoa(unescape(encodeURIComponent(text)));
+}
+
+export const POST: RequestHandler = async (event) => {
+ const { user } = requireAuth(event);
+
+ const isAmbassador = await db
+ .select({ userId: ambassadorPathway.userId })
+ .from(ambassadorPathway)
+ .where(eq(ambassadorPathway.userId, user.id))
+ .limit(1);
+
+ if (!user.isAdmin && isAmbassador.length === 0) {
+ throw error(403, 'Access denied - admin or ambassador only');
+ }
+
+ let body: any;
+ try {
+ body = await event.request.json();
+ } catch {
+ throw error(400, 'Invalid JSON body');
+ }
+ const orderId = body?.orderId;
+ const carrier = body?.carrier;
+ if (!orderId || typeof orderId !== 'string') throw error(400, 'Order ID required');
+ if (carrier && !['auto', 'canada_post', 'chitchats', 'lettermail'].includes(carrier)) {
+ throw error(400, 'Invalid carrier');
+ }
+
+ const order = await db.query.warehouseOrder.findFirst({
+ where: eq(warehouseOrder.id, orderId),
+ with: {
+ items: {
+ with: {
+ warehouseItem: true
+ }
+ }
+ }
+ });
+
+ if (!order) throw error(404, 'Order not found');
+
+ if (!user.isAdmin && order.createdById !== user.id) {
+ throw error(403, 'Access denied - you do not own this order');
+ }
+
+ // If the order already has a label, re-fetch and return it as base64
+ if (order.labelUrl) {
+ let labelUrl = order.labelUrl;
+ if (!labelUrl.startsWith('data:')) {
+ try {
+ const parsed = new URL(labelUrl);
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
+ throw error(400, 'Invalid label URL protocol');
+ }
+ } catch (e: any) {
+ if (e?.status) throw e;
+ throw error(400, 'Invalid label URL');
+ }
+ const labelRes = await fetch(labelUrl);
+ console.log('Label fetch status:', labelRes.status, labelRes.statusText);
+ if (labelRes.ok) {
+ const labelBuffer = await labelRes.arrayBuffer();
+ console.log('Label PDF size:', labelBuffer.byteLength, 'bytes');
+ labelUrl = `data:application/pdf;base64,${arrayBufferToBase64(labelBuffer)}`;
+ } else {
+ const errBody = await labelRes.text();
+ console.error('Label fetch failed:', errBody);
+ throw error(502, `Failed to fetch label PDF: ${labelRes.status}`);
+ }
+ }
+ return json({
+ trackingNumber: order.trackingNumber,
+ labelUrl,
+ packingSlipBase64: buildPackingSlipBase64(order),
+ shippingMethod: order.shippingMethod || ''
+ });
+ }
+
+ // Prevent duplicate shipment creation from concurrent requests
+ if (order.status === 'SHIPPED') {
+ throw error(409, 'Order has already been shipped');
+ }
+
+ // Calculate package totals from items
+ const MAX_HEIGHT_IN = 48; // Cap at 48 inches to prevent unrealistic box dimensions
+ let totalWeight = 0;
+ let maxLength = 0;
+ let maxWidth = 0;
+ let totalHeight = 0;
+
+ for (const oi of order.items) {
+ const item = oi.warehouseItem;
+ totalWeight += item.weightGrams * oi.quantity;
+ maxLength = Math.max(maxLength, item.lengthIn);
+ maxWidth = Math.max(maxWidth, item.widthIn);
+ totalHeight += item.heightIn * oi.quantity;
+ }
+ totalHeight = Math.min(totalHeight, MAX_HEIGHT_IN);
+
+ const packingSlipBase64 = buildPackingSlipBase64(order);
+
+ let trackingNumber: string | null = null;
+ let labelUrl: string | null = null;
+ let shippingMethod: string;
+
+ const isChitChatsOrder = !carrier || carrier === 'auto'
+ ? order.estimatedServiceCode?.startsWith('CHITCHATS.')
+ : carrier === 'chitchats';
+
+ if (isChitChatsOrder) {
+ // ── CHIT CHATS PATH ──
+ shippingMethod = 'chitchats';
+
+ try {
+ const result = await createChitChatsShipment({
+ order,
+ weightGrams: totalWeight,
+ lengthIn: maxLength,
+ widthIn: maxWidth,
+ heightIn: totalHeight
+ });
+ trackingNumber = result.trackingNumber;
+ labelUrl = result.labelBase64;
+ } catch (e: any) {
+ console.error('Chit Chats Create Shipment error:', e.message);
+ throw error(502, 'Chit Chats shipment creation failed');
+ }
+ } else if (carrier === 'lettermail' || ((!carrier || carrier === 'auto') && isLettermail(order.estimatedServiceName))) {
+ // ── LETTERMAIL PATH: Use Theseus/mail.hackclub.com ──
+ shippingMethod = 'lettermail';
+
+ const theseusApiKey = env.THESEUS_API_KEY;
+ const theseusQueueSlug = env.THESEUS_QUEUE_SLUG;
+
+ if (!theseusApiKey || !theseusQueueSlug) {
+ throw error(500, 'Theseus API not configured (THESEUS_API_KEY and THESEUS_QUEUE_SLUG required)');
+ }
+
+ const theseusBody = {
+ address: {
+ first_name: order.firstName,
+ last_name: order.lastName,
+ line_1: order.addressLine1,
+ line_2: order.addressLine2 || undefined,
+ city: order.city,
+ state: order.stateProvince,
+ postal_code: order.postalCode || undefined,
+ country: order.country
+ },
+ idempotency_key: `warehouse-order-${order.id}`,
+ metadata: {
+ warehouse_order_id: order.id,
+ fulfillment_id: order.fulfillmentId
+ }
+ };
+
+ const theseusUrl = `${env.THESEUS_BASE_URL || 'https://mail.hackclub.com'}/api/v1/letter_queues/instant/${theseusQueueSlug}`;
+
+ const theseusRes = await fetch(theseusUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${theseusApiKey}`
+ },
+ body: JSON.stringify(theseusBody)
+ });
+
+ if (!theseusRes.ok) {
+ const errBody = await theseusRes.text();
+ console.error('Theseus API error:', errBody);
+ throw error(502, `Lettermail label creation failed: ${theseusRes.status}`);
+ }
+
+ const theseusData = await theseusRes.json();
+ trackingNumber = theseusData.id || null;
+ const rawLabelUrl = theseusData.label_url || null;
+
+ // Fetch the label PDF and convert to base64 data URL so the frontend can print it via qz-tray
+ if (rawLabelUrl) {
+ try {
+ const labelRes = await fetch(rawLabelUrl);
+ if (labelRes.ok) {
+ const labelBuffer = await labelRes.arrayBuffer();
+ const labelBase64 = arrayBufferToBase64(labelBuffer);
+ labelUrl = `data:application/pdf;base64,${labelBase64}`;
+ } else {
+ labelUrl = rawLabelUrl;
+ }
+ } catch {
+ labelUrl = rawLabelUrl;
+ }
+ }
+
+ // Mark the letter as printed in Theseus
+ if (trackingNumber) {
+ const theseusBaseUrl = env.THESEUS_BASE_URL || 'https://mail.hackclub.com';
+ await fetch(`${theseusBaseUrl}/api/v1/letters/${trackingNumber}/mark_printed`, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${theseusApiKey}` }
+ }).catch((e) => console.error('Failed to mark Theseus letter as printed:', e));
+ }
+
+ } else {
+ // ── CANADA POST PARCEL PATH ──
+ shippingMethod = 'canada_post';
+
+ const weightKg = totalWeight * GRAMS_TO_KG;
+ const lengthCm = inchesToCm(maxLength);
+ const widthCm = inchesToCm(maxWidth);
+ const heightCm = inchesToCm(totalHeight);
+ let serviceCode = order.estimatedServiceName
+ ? getServiceCode(order.estimatedServiceName)
+ : order.country === 'CA' ? 'DOM.RP'
+ : order.country === 'US' ? 'USA.TP'
+ : 'INT.TP';
+ if (!env.CP_CONTRACT_ID) {
+ if (serviceCode === 'USA.SP.AIR') serviceCode = 'USA.TP';
+ }
+ console.log(`Creating shipment: country=${order.country}, estimatedService=${order.estimatedServiceName}, serviceCode=${serviceCode}`);
+
+ // Try creating shipment, fall back to alternate international services if unavailable
+ const fallbackCodes: string[] = [];
+ const intFallbacks = ['INT.TP', 'INT.SP.AIR', 'INT.IP', 'INT.IP.SURF'];
+ if (env.CP_CONTRACT_ID) intFallbacks.unshift('INT.XP');
+ if (serviceCode.startsWith('INT.')) {
+ for (const code of intFallbacks) {
+ if (code !== serviceCode) fallbackCodes.push(code);
+ }
+ }
+ if (serviceCode === 'INT.XP') fallbackCodes.push('INT.TP');
+ let lastError: any;
+ for (const code of [serviceCode, ...fallbackCodes]) {
+ try {
+ const result = await createShipment({ order, weightKg, lengthCm, widthCm, heightCm, serviceCode: code });
+ trackingNumber = result.trackingPin;
+ labelUrl = result.labelBase64;
+ if (code !== serviceCode) console.log(`Used fallback service ${code} instead of ${serviceCode}`);
+ lastError = null;
+ break;
+ } catch (e: any) {
+ console.error(`Canada Post Create Shipment error (${code}):`, e.message);
+ lastError = e;
+ }
+ }
+ // If Canada Post failed for international, try Chit Chats as fallback
+ if (lastError && order.country !== 'CA' && env.CHITCHATS_ACCESS_TOKEN && env.CHITCHATS_CLIENT_ID) {
+ console.log(`Canada Post unavailable for ${order.country}, trying Chit Chats fallback`);
+ try {
+ const result = await createChitChatsShipment({
+ order,
+ weightGrams: totalWeight,
+ lengthIn: maxLength,
+ widthIn: maxWidth,
+ heightIn: totalHeight
+ });
+ trackingNumber = result.trackingNumber;
+ labelUrl = result.labelBase64;
+ shippingMethod = 'chitchats';
+ lastError = null;
+ } catch (e: any) {
+ console.error('Chit Chats fallback also failed:', e.message);
+ }
+ }
+ if (lastError) {
+ throw error(502, `Shipment creation failed: ${lastError.message}`);
+ }
+ }
+
+ // Update the order with tracking info
+ await db.update(warehouseOrder)
+ .set({
+ trackingNumber,
+ labelUrl,
+ shippingMethod,
+ status: 'SHIPPED',
+ updatedAt: new Date()
+ })
+ .where(eq(warehouseOrder.id, orderId));
+
+ return json({
+ trackingNumber,
+ labelUrl,
+ packingSlipBase64,
+ shippingMethod
+ });
+};
diff --git a/resolution-frontend/src/routes/api/qz/cert/+server.ts b/resolution-frontend/src/routes/api/qz/cert/+server.ts
new file mode 100644
index 0000000..6654852
--- /dev/null
+++ b/resolution-frontend/src/routes/api/qz/cert/+server.ts
@@ -0,0 +1,14 @@
+import { env } from '$env/dynamic/private';
+import { requireAuth } from '$lib/server/auth/guard';
+import type { RequestHandler } from './$types';
+
+export const GET: RequestHandler = async (event) => {
+ requireAuth(event);
+ const cert = env.QZ_CERTIFICATE?.replace(/\\n/g, '\n');
+ if (!cert) {
+ return new Response('QZ certificate not configured', { status: 500 });
+ }
+ return new Response(cert, {
+ headers: { 'Content-Type': 'text/plain' }
+ });
+};
diff --git a/resolution-frontend/src/routes/api/qz/sign/+server.ts b/resolution-frontend/src/routes/api/qz/sign/+server.ts
new file mode 100644
index 0000000..f8858c2
--- /dev/null
+++ b/resolution-frontend/src/routes/api/qz/sign/+server.ts
@@ -0,0 +1,31 @@
+import { env } from '$env/dynamic/private';
+import { createSign } from 'crypto';
+import { requireAuth } from '$lib/server/auth/guard';
+import type { RequestHandler } from './$types';
+
+export const POST: RequestHandler = async (event) => {
+ requireAuth(event);
+
+ const toSign = await event.request.text();
+
+ const privateKey = env.QZ_PRIVATE_KEY?.replace(/\\n/g, '\n');
+ const password = env.QZ_PK_PASSWORD;
+
+ if (!privateKey) {
+ return new Response('QZ private key not configured', { status: 500 });
+ }
+
+ try {
+ const sign = createSign('SHA512');
+ sign.update(toSign);
+ const signature = sign.sign(
+ { key: privateKey, passphrase: password || '' },
+ 'base64'
+ );
+ return new Response(signature, {
+ headers: { 'Content-Type': 'text/plain' }
+ });
+ } catch (e) {
+ return new Response('Signing failed', { status: 500 });
+ }
+};
diff --git a/resolution-frontend/src/routes/api/shipping-rates/+server.ts b/resolution-frontend/src/routes/api/shipping-rates/+server.ts
new file mode 100644
index 0000000..f2d897c
--- /dev/null
+++ b/resolution-frontend/src/routes/api/shipping-rates/+server.ts
@@ -0,0 +1,147 @@
+import { json, error } from '@sveltejs/kit';
+import type { RequestHandler } from './$types';
+import { env } from '$env/dynamic/private';
+import { requireAuth } from '$lib/server/auth/guard';
+import { validateJson, shippingRateSchema } from '$lib/server/validation';
+import { db } from '$lib/server/db';
+import { ambassadorPathway } from '$lib/server/db/schema';
+import { eq } from 'drizzle-orm';
+import { GRAMS_TO_KG, inchesToCm, fetchRates, getLetterMailOptions, calculateZonosDuties } from '$lib/server/canada-post';
+import { getCadToUsdRate } from '$lib/server/exchange-rate';
+import { fetchChitChatsRates } from '$lib/server/chit-chats';
+
+export const POST: RequestHandler = async (event) => {
+ const { user } = requireAuth(event);
+
+ const assignments = await db
+ .select()
+ .from(ambassadorPathway)
+ .where(eq(ambassadorPathway.userId, user.id));
+
+ if (assignments.length === 0 && !user.isAdmin) {
+ throw error(403, 'You are not an ambassador');
+ }
+
+ const data = await validateJson(shippingRateSchema, event.request);
+
+ if ((data.country === 'CA' || data.country === 'US') && !data.postalCode) {
+ throw error(400, 'Postal/ZIP code is required for Canadian and US destinations');
+ }
+
+ if (!env.CP_ORIGIN_POSTAL_CODE) {
+ throw error(500, 'Canada Post API not configured');
+ }
+
+ const weightKg = data.weight * GRAMS_TO_KG;
+
+ // For flats/envelopes, snap to available envelope sizes (4x6in or 6x9in).
+ // If too large for 6x9, treat as a bubble packet with 0.5in thickness.
+ let effectiveLength = data.length;
+ let effectiveWidth = data.width;
+ let effectivePackageType = data.packageType;
+ if (data.packageType === 'flat' || data.packageType === 'envelope') {
+ const l = Math.max(data.length, data.width);
+ const w = Math.min(data.length, data.width);
+ if (l <= 6 && w <= 4) {
+ effectiveLength = 6;
+ effectiveWidth = 4;
+ } else if (l <= 9 && w <= 6) {
+ effectiveLength = 9;
+ effectiveWidth = 6;
+ } else {
+ // Too large for available envelopes — treat as bubble packet
+ effectiveLength = l;
+ effectiveWidth = w;
+ effectivePackageType = 'box';
+ }
+ }
+
+ const lengthCm = inchesToCm(effectiveLength);
+ const widthCm = inchesToCm(effectiveWidth);
+ const heightCm = effectivePackageType === 'box'
+ ? inchesToCm(data.packageType === 'box' ? data.height : 0.5)
+ : 0.5;
+
+ const lettermailOptions = getLetterMailOptions(data.weight, lengthCm, widthCm, heightCm, data.country);
+
+ let parcelRates: Array<{
+ serviceName: string;
+ serviceCode: string;
+ priceDetails: { base: number; gst: number; pst: number; hst: number; total: number };
+ deliveryDate: string;
+ transitDays: string;
+ currency: string;
+ }> = [];
+ try {
+ parcelRates = await fetchRates({ country: data.country, postalCode: data.postalCode, weightKg, lengthCm, widthCm, heightCm });
+ } catch (err) {
+ console.error('Parcel rate lookup failed:', err);
+ }
+
+ let chitChatsRates: Array<{
+ serviceName: string;
+ serviceCode: string;
+ priceDetails: { base: number; gst: number; pst: number; hst: number; total: number };
+ deliveryDate: string;
+ transitDays: string;
+ currency: string;
+ }> = [];
+ try {
+ if (env.CHITCHATS_ACCESS_TOKEN && env.CHITCHATS_CLIENT_ID) {
+ // Chit Chats requires a temporary shipment to return rates — use actual address data
+ chitChatsRates = await fetchChitChatsRates({
+ country: data.country,
+ postalCode: data.postalCode,
+ province: data.province,
+ name: 'Rate Quote',
+ address1: data.street,
+ city: data.city,
+ weightGrams: data.weight,
+ lengthIn: effectiveLength,
+ widthIn: effectiveWidth,
+ heightIn: data.packageType === 'box' ? data.height : 0.5,
+ valueCad: 1.00
+ });
+ }
+ } catch (err) {
+ console.error('Chit Chats rate lookup failed:', err);
+ }
+
+ const allRates = [...lettermailOptions, ...parcelRates, ...chitChatsRates];
+
+ let zonosDuties = null;
+ if (data.country === 'US' && data.items && data.items.length > 0) {
+ const cheapestRate = allRates.reduce((min, r) => r.priceDetails.total < min ? r.priceDetails.total : min, Infinity);
+ const cadToUsd = await getCadToUsdRate();
+ const shippingCostCad = cheapestRate !== Infinity ? cheapestRate / cadToUsd : 0;
+
+ zonosDuties = await calculateZonosDuties({
+ items: data.items.map((item: any) => ({
+ hsCode: item.hsCode || '',
+ valueCadCents: item.costCents || 0,
+ quantity: item.quantity || 1,
+ sku: item.sku || '',
+ description: item.name || 'Merchandise'
+ })),
+ shippingCostCad,
+ destinationAddress: {
+ city: data.city || '',
+ state: data.province || '',
+ postalCode: data.postalCode || '',
+ country: data.country
+ }
+ });
+ }
+
+ return json({
+ rates: allRates,
+ origin: env.CP_ORIGIN_POSTAL_CODE,
+ destination: {
+ country: data.country,
+ city: data.city,
+ province: data.province,
+ postalCode: data.postalCode
+ },
+ ...(zonosDuties ? { zonosDuties } : {})
+ });
+};
diff --git a/resolution-frontend/src/routes/app/+page.svelte b/resolution-frontend/src/routes/app/+page.svelte
index 79b64bb..0f8e186 100644
--- a/resolution-frontend/src/routes/app/+page.svelte
+++ b/resolution-frontend/src/routes/app/+page.svelte
@@ -46,13 +46,17 @@
Welcome, {data.user.firstName || data.user.email}!