feat: enhance demo modes across 12 modules + add inbox/sharp/x402 deps

Improve interactive demos for notes, maps, network, vote, calendar,
choices, trips, work, books, wallet, and inbox modules with richer
mock data and better mobile responsiveness. Update server routing
and landing page. Add sharp, imapflow, mailparser, and x402 dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-28 06:48:51 +00:00
parent 7e5a8624d7
commit 01fa8b1ba5
15 changed files with 1900 additions and 362 deletions

465
bun.lock
View File

@ -6,15 +6,22 @@
"name": "rspace-online", "name": "rspace-online",
"dependencies": { "dependencies": {
"@automerge/automerge": "^2.2.8", "@automerge/automerge": "^2.2.8",
"@aws-sdk/client-s3": "^3.700.0",
"@encryptid/sdk": "file:../encryptid-sdk", "@encryptid/sdk": "file:../encryptid-sdk",
"@lit/reactive-element": "^2.0.4", "@lit/reactive-element": "^2.0.4",
"@x402/core": "^2.3.1",
"@x402/evm": "^2.3.1",
"hono": "^4.11.7", "hono": "^4.11.7",
"imapflow": "^1.0.170",
"mailparser": "^3.7.2",
"nodemailer": "^6.9.0", "nodemailer": "^6.9.0",
"perfect-arrows": "^0.3.7", "perfect-arrows": "^0.3.7",
"perfect-freehand": "^1.2.2", "perfect-freehand": "^1.2.2",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"sharp": "^0.33.0",
}, },
"devDependencies": { "devDependencies": {
"@types/mailparser": "^3.4.0",
"@types/node": "^22.10.1", "@types/node": "^22.10.1",
"@types/nodemailer": "^6.4.0", "@types/nodemailer": "^6.4.0",
"bun-types": "^1.1.38", "bun-types": "^1.1.38",
@ -26,8 +33,92 @@
}, },
}, },
"packages": { "packages": {
"@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
"@automerge/automerge": ["@automerge/automerge@2.2.9", "", { "dependencies": { "uuid": "^9.0.0" } }, "sha512-6HM52Ops79hAQBWMg/t0MNfGOdEiXyenQjO9F1hKZq0RWDsMLpPa1SzRy/C4/4UyX67sTHuA5CwBpH34SpfZlA=="], "@automerge/automerge": ["@automerge/automerge@2.2.9", "", { "dependencies": { "uuid": "^9.0.0" } }, "sha512-6HM52Ops79hAQBWMg/t0MNfGOdEiXyenQjO9F1hKZq0RWDsMLpPa1SzRy/C4/4UyX67sTHuA5CwBpH34SpfZlA=="],
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
"@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="],
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1000.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.15", "@aws-sdk/credential-provider-node": "^3.972.14", "@aws-sdk/middleware-bucket-endpoint": "^3.972.6", "@aws-sdk/middleware-expect-continue": "^3.972.6", "@aws-sdk/middleware-flexible-checksums": "^3.973.1", "@aws-sdk/middleware-host-header": "^3.972.6", "@aws-sdk/middleware-location-constraint": "^3.972.6", "@aws-sdk/middleware-logger": "^3.972.6", "@aws-sdk/middleware-recursion-detection": "^3.972.6", "@aws-sdk/middleware-sdk-s3": "^3.972.15", "@aws-sdk/middleware-ssec": "^3.972.6", "@aws-sdk/middleware-user-agent": "^3.972.15", "@aws-sdk/region-config-resolver": "^3.972.6", "@aws-sdk/signature-v4-multi-region": "^3.996.3", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@aws-sdk/util-user-agent-browser": "^3.972.6", "@aws-sdk/util-user-agent-node": "^3.973.0", "@smithy/config-resolver": "^4.4.9", "@smithy/core": "^3.23.6", "@smithy/eventstream-serde-browser": "^4.2.10", "@smithy/eventstream-serde-config-resolver": "^4.3.10", "@smithy/eventstream-serde-node": "^4.2.10", "@smithy/fetch-http-handler": "^5.3.11", "@smithy/hash-blob-browser": "^4.2.11", "@smithy/hash-node": "^4.2.10", "@smithy/hash-stream-node": "^4.2.10", "@smithy/invalid-dependency": "^4.2.10", "@smithy/md5-js": "^4.2.10", "@smithy/middleware-content-length": "^4.2.10", "@smithy/middleware-endpoint": "^4.4.20", "@smithy/middleware-retry": "^4.4.37", "@smithy/middleware-serde": "^4.2.11", "@smithy/middleware-stack": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/node-http-handler": "^4.4.12", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-body-length-node": "^4.2.2", "@smithy/util-defaults-mode-browser": "^4.3.36", "@smithy/util-defaults-mode-node": "^4.2.39", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/util-stream": "^4.5.15", "@smithy/util-utf8": "^4.2.1", "@smithy/util-waiter": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-7kPy33qNGq3NfwHC0412T6LDK1bp4+eiPzetX0sVd9cpTSXuQDKpoOFnB0Njj6uZjJDcLS3n2OeyarwwgkQ0Ow=="],
"@aws-sdk/core": ["@aws-sdk/core@3.973.15", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws-sdk/xml-builder": "^3.972.8", "@smithy/core": "^3.23.6", "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-AlC0oQ1/mdJ8vCIqu524j5RB7M8i8E24bbkZmya1CuiQxkY7SdIZAyw7NDNMGaNINQFq/8oGRMX0HeOfCVsl/A=="],
"@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.3", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UExeK+EFiq5LAcbHm96CQLSia+5pvpUVSAsVApscBzayb7/6dJBJKwV4/onsk4VbWSmqxDMcfuTD+pC4RxgZHg=="],
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.13", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-6ljXKIQ22WFKyIs1jbORIkGanySBHaPPTOI4OxACP5WXgbcR0nDYfqNJfXEGwCK7IzHdNbCSFsNKKs0qCexR8Q=="],
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/types": "^3.973.4", "@smithy/fetch-http-handler": "^5.3.11", "@smithy/node-http-handler": "^4.4.12", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.15", "tslib": "^2.6.2" } }, "sha512-dJuSTreu/T8f24SHDNTjd7eQ4rabr0TzPh2UTCwYexQtzG3nTDKm1e5eIdhiroTMDkPEJeY+WPkA6F9wod/20A=="],
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.13", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/credential-provider-env": "^3.972.13", "@aws-sdk/credential-provider-http": "^3.972.15", "@aws-sdk/credential-provider-login": "^3.972.13", "@aws-sdk/credential-provider-process": "^3.972.13", "@aws-sdk/credential-provider-sso": "^3.972.13", "@aws-sdk/credential-provider-web-identity": "^3.972.13", "@aws-sdk/nested-clients": "^3.996.3", "@aws-sdk/types": "^3.973.4", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-JKSoGb7XeabZLBJptpqoZIFbROUIS65NuQnEHGOpuT9GuuZwag2qciKANiDLFiYk4u8nSrJC9JIOnWKVvPVjeA=="],
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.13", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/nested-clients": "^3.996.3", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RtYcrxdnJHKY8MFQGLltCURcjuMjnaQpAxPE6+/QEdDHHItMKZgabRe/KScX737F9vJMQsmJy9EmMOkCnoC1JQ=="],
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.14", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.13", "@aws-sdk/credential-provider-http": "^3.972.15", "@aws-sdk/credential-provider-ini": "^3.972.13", "@aws-sdk/credential-provider-process": "^3.972.13", "@aws-sdk/credential-provider-sso": "^3.972.13", "@aws-sdk/credential-provider-web-identity": "^3.972.13", "@aws-sdk/types": "^3.973.4", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-WqoC2aliIjQM/L3oFf6j+op/enT2i9Cc4UTxxMEKrJNECkq4/PlKE5BOjSYFcq6G9mz65EFbXJh7zOU4CvjSKQ=="],
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.13", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-rsRG0LQA4VR+jnDyuqtXi2CePYSmfm5GNL9KxiW8DSe25YwJSr06W8TdUfONAC+rjsTI+aIH2rBGG5FjMeANrw=="],
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.13", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/nested-clients": "^3.996.3", "@aws-sdk/token-providers": "3.999.0", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-fr0UU1wx8kNHDhTQBXioc/YviSW8iXuAxHvnH7eQUtn8F8o/FU3uu6EUMvAQgyvn7Ne5QFnC0Cj0BFlwCk+RFw=="],
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.13", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/nested-clients": "^3.996.3", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-a6iFMh1pgUH0TdcouBppLJUfPM7Yd3R9S1xFodPtCRoLqCz2RQFA3qjA8x4112PVYXEd4/pHX2eihapq39w0rA=="],
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-3H2bhvb7Cb/S6WFsBy/Dy9q2aegC9JmGH1inO8Lb2sWirSqpLJlZmvQHPE29h2tIxzv6el/14X/tLCQ8BQU6ZQ=="],
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-QMdffpU+GkSGC+bz6WdqlclqIeCsOfgX8JFZ5xvwDtX+UTj4mIXm3uXu7Ko6dBseRcJz1FA6T9OmlAAY6JgJUg=="],
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.973.1", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.15", "@aws-sdk/crc64-nvme": "^3.972.3", "@aws-sdk/types": "^3.973.4", "@smithy/is-array-buffer": "^4.2.1", "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.10", "@smithy/util-stream": "^4.5.15", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-QLXsxsI6VW8LuGK+/yx699wzqP/NMCGk/hSGP+qtB+Lcff+23UlbahyouLlk+nfT7Iu021SkXBhnAuVd6IZcPw=="],
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-5XHwjPH1lHB+1q4bfC7T8Z5zZrZXfaLcjSMwTd1HPSPrCmPFMbg3UQ5vgNWcVj0xoX4HWqTGkSf2byrjlnRg5w=="],
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-XdZ2TLwyj3Am6kvUc67vquQvs6+D8npXvXgyEUJAdkUDx5oMFJKOqpK+UpJhVDsEL068WAJl2NEGzbSik7dGJQ=="],
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-iFnaMFMQdljAPrvsCVKYltPt2j40LQqukAbXvW7v0aL5I+1GO7bZ/W8m12WxW3gwyK5p5u1WlHg8TSAizC5cZw=="],
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-dY4v3of5EEMvik6+UDwQ96KfUFDk8m1oZDdkSc5lwi4o7rFrjnv0A+yTV+gu230iybQZnKgDLg/rt2P3H+Vscw=="],
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-arn-parser": "^3.972.2", "@smithy/core": "^3.23.6", "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-stream": "^4.5.15", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-WDLgssevOU5BFx1s8jA7jj6cE5HuImz28sy9jKOaVtz0AW1lYqSzotzdyiybFaBcQTs5zxXOb2pUfyMxgEKY3Q=="],
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-acvMUX9jF4I2Ew+Z/EA6gfaFaz9ehci5wxBmXCZeulLuv8m+iGf6pY9uKz8TPjg39bdAz3hxoE0eLP8Qz+IYlA=="],
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.15", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@smithy/core": "^3.23.6", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-ABlFVcIMmuRAwBT+8q5abAxOr7WmaINirDJBnqGY5b5jSDo00UMlg/G4a0xoAgwm6oAECeJcwkvDlxDwKf58fQ=="],
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.3", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.15", "@aws-sdk/middleware-host-header": "^3.972.6", "@aws-sdk/middleware-logger": "^3.972.6", "@aws-sdk/middleware-recursion-detection": "^3.972.6", "@aws-sdk/middleware-user-agent": "^3.972.15", "@aws-sdk/region-config-resolver": "^3.972.6", "@aws-sdk/types": "^3.973.4", "@aws-sdk/util-endpoints": "^3.996.3", "@aws-sdk/util-user-agent-browser": "^3.972.6", "@aws-sdk/util-user-agent-node": "^3.973.0", "@smithy/config-resolver": "^4.4.9", "@smithy/core": "^3.23.6", "@smithy/fetch-http-handler": "^5.3.11", "@smithy/hash-node": "^4.2.10", "@smithy/invalid-dependency": "^4.2.10", "@smithy/middleware-content-length": "^4.2.10", "@smithy/middleware-endpoint": "^4.4.20", "@smithy/middleware-retry": "^4.4.37", "@smithy/middleware-serde": "^4.2.11", "@smithy/middleware-stack": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/node-http-handler": "^4.4.12", "@smithy/protocol-http": "^5.3.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-body-length-node": "^4.2.2", "@smithy/util-defaults-mode-browser": "^4.3.36", "@smithy/util-defaults-mode-node": "^4.2.39", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-AU5TY1V29xqwg/MxmA2odwysTez+ccFAhmfRJk+QZT5HNv90UTA9qKd1J9THlsQkvmH7HWTEV1lDNxkQO5PzNw=="],
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/config-resolver": "^4.4.9", "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-Aa5PusHLXAqLTX1UKDvI3pHQJtIsF7Q+3turCHqfz/1F61/zDMWfbTC8evjhrrYVAtz9Vsv3SJ/waSUeu7B6gw=="],
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.3", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.15", "@aws-sdk/types": "^3.973.4", "@smithy/protocol-http": "^5.3.10", "@smithy/signature-v4": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-gQYI/Buwp0CAGQxY7mR5VzkP56rkWq2Y1ROkFuXh5XY94DsSjJw62B3I0N0lysQmtwiL2ht2KHI9NylM/RP4FA=="],
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.999.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.15", "@aws-sdk/nested-clients": "^3.996.3", "@aws-sdk/types": "^3.973.4", "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-cx0hHUlgXULfykx4rdu/ciNAJaa3AL5xz3rieCz7NKJ68MJwlj3664Y8WR5MGgxfyYJBdamnkjNSx5Kekuc0cg=="],
"@aws-sdk/types": ["@aws-sdk/types@3.973.4", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q=="],
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg=="],
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-endpoints": "^3.3.1", "tslib": "^2.6.2" } }, "sha512-yWIQSNiCjykLL+ezN5A+DfBb1gfXTytBxm57e64lYmwxDHNmInYHRJYYRAGWG1o77vKEiWaw4ui28e3yb1k5aQ=="],
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="],
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.4", "@smithy/types": "^4.13.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-Fwr/llD6GOrFgQnKaI2glhohdGuBDfHfora6iG9qsBBBR8xv1SdCSwbtf5CWlUdCw5X7g76G/9Hf0Inh0EmoxA=="],
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.15", "@aws-sdk/types": "^3.973.4", "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-A9J2G4Nf236e9GpaC1JnA8wRn6u6GjnOXiTwBLA6NUJhlBTIGfrTy+K1IazmF8y+4OFdW3O5TZlhyspJMqiqjA=="],
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.8", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-Ql8elcUdYCha83Ol7NznBsgN5GVZnv3vUd86fEc6waU6oUdY0T1O9NODkEEOS/Uaogr87avDrUC6DSeM4oXjZg=="],
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@encryptid/sdk": ["@encryptid/sdk@file:../encryptid-sdk", { "dependencies": { "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "hono": "^4.11.0" }, "devDependencies": { "@types/react": "^19.0.0", "typescript": "^5.7.0" }, "peerDependencies": { "next": ">=14.0.0", "react": ">=18.0.0" }, "optionalPeers": ["next", "react"] }], "@encryptid/sdk": ["@encryptid/sdk@file:../encryptid-sdk", { "dependencies": { "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "hono": "^4.11.0" }, "devDependencies": { "@types/react": "^19.0.0", "typescript": "^5.7.0" }, "peerDependencies": { "next": ">=14.0.0", "react": ">=18.0.0" }, "optionalPeers": ["next", "react"] }],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
@ -82,14 +173,56 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"@lit-labs/ssr-dom-shim": ["@lit-labs/ssr-dom-shim@1.5.0", "", {}, "sha512-HLomZXMmrCFHSRKESF5vklAKsDY7/fsT/ZhqCu3V0UoW/Qbv8wxmO4W9bx4KnCCF2Zak4yuk+AGraK/bPmI4kA=="], "@lit-labs/ssr-dom-shim": ["@lit-labs/ssr-dom-shim@1.5.0", "", {}, "sha512-HLomZXMmrCFHSRKESF5vklAKsDY7/fsT/ZhqCu3V0UoW/Qbv8wxmO4W9bx4KnCCF2Zak4yuk+AGraK/bPmI4kA=="],
"@lit/reactive-element": ["@lit/reactive-element@2.1.2", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0" } }, "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A=="], "@lit/reactive-element": ["@lit/reactive-element@2.1.2", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0" } }, "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A=="],
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
"@noble/curves": ["@noble/curves@2.0.1", "", { "dependencies": { "@noble/hashes": "2.0.1" } }, "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw=="], "@noble/curves": ["@noble/curves@2.0.1", "", { "dependencies": { "@noble/hashes": "2.0.1" } }, "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw=="],
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="], "@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
"@rollup/plugin-virtual": ["@rollup/plugin-virtual@3.0.2", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A=="], "@rollup/plugin-virtual": ["@rollup/plugin-virtual@3.0.2", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.54.0", "", { "os": "android", "cpu": "arm" }, "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng=="],
@ -136,6 +269,126 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="],
"@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="],
"@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="],
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g=="],
"@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-y5d4xRiD6TzeP5BWlb+Ig/VFqF+t9oANNhGeMqyzU7obw7FYgTgVi50i5JqBTeKp+TABeDIeeXFZdz65RipNtA=="],
"@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.2", "", { "dependencies": { "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-QzzYIlf4yg0w5TQaC9VId3B3ugSk1MI/wb7tgcHtd7CBV9gNRKZrhc2EPSxSZuDy10zUZ0lomNMgkc6/VVe8xg=="],
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.9", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.1", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q=="],
"@smithy/core": ["@smithy/core@3.23.6", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.11", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-stream": "^4.5.15", "@smithy/util-utf8": "^4.2.1", "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg=="],
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.10", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.10", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-A4ynrsFFfSXUHicfTcRehytppFBcY3HQxEGYiyGktPIOye3Ot7fxpiy4VR42WmtGI4Wfo6OXt/c1Ky1nUFxYYQ=="],
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.10", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-0xupsu9yj9oDVuQ50YCTS9nuSYhGlrwqdaKQel9y2Fz7LU9fNErVlw9N0o4pm4qqvWEGbSTI4HKc6XJfB30MVw=="],
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-8kn6sinrduk0yaYHMJDsNuiFpXwQwibR7n/4CDUqn4UgaG+SeBHu5jHGFdU9BLFAM7Q4/gvr9RYxBHz9/jKrhA=="],
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.10", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-uUrxPGgIffnYfvIOUmBM5i+USdEBRTdh7mLPttjphgtooxQ8CtdO1p6K5+Q4BBAZvKlvtJ9jWyrWpBJYzBKsyQ=="],
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.10", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-aArqzOEvcs2dK+xQVCgLbpJQGfZihw8SD4ymhkwNTtwKbnrzdhJsFDKuMQnam2kF69WzgJYOU5eJlCx+CA32bw=="],
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/querystring-builder": "^4.2.10", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g=="],
"@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.11", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.1", "@smithy/chunked-blob-reader-native": "^4.2.2", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-DrcAx3PM6AEbWZxsKl6CWAGnVwiz28Wp1ZhNu+Hi4uI/6C1PIZBIaPM2VoqBDAsOWbM6ZVzOEQMxFLLdmb4eBQ=="],
"@smithy/hash-node": ["@smithy/hash-node@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w=="],
"@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-w78xsYrOlwXKwN5tv1GnKIRbHb1HygSpeZMP6xDxCPGf1U/xDHjCpJu64c5T35UKyEPwa0bPeIcvU69VY3khUA=="],
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org=="],
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q=="],
"@smithy/md5-js": ["@smithy/md5-js@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-Op+Dh6dPLWTjWITChFayDllIaCXRofOed8ecpggTC5fkh8yXes0vAEX7gRUfjGK+TlyxoCAA05gHbZW/zB9JwQ=="],
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.10", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w=="],
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.20", "", { "dependencies": { "@smithy/core": "^3.23.6", "@smithy/middleware-serde": "^4.2.11", "@smithy/node-config-provider": "^4.3.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw=="],
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.37", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/service-error-classification": "^4.2.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "sha512-/1psZZllBBSQ7+qo5+hhLz7AEPGLx3Z0+e3ramMBEuPK2PfvLK4SrncDB9VegX5mBn+oP/UTDrM6IHrFjvX1ZA=="],
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg=="],
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA=="],
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.10", "", { "dependencies": { "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w=="],
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.12", "", { "dependencies": { "@smithy/abort-controller": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/querystring-builder": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-zo1+WKJkR9x7ZtMeMDAAsq2PufwiLDmkhcjpWPRRkmeIuOm6nq1qjFICSZbnjBvD09ei8KMo26BWxsu2BUU+5w=="],
"@smithy/property-provider": ["@smithy/property-provider@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw=="],
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A=="],
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA=="],
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q=="],
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg=="],
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug=="],
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.10", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.1", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-uri-escape": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA=="],
"@smithy/smithy-client": ["@smithy/smithy-client@4.12.0", "", { "dependencies": { "@smithy/core": "^3.23.6", "@smithy/middleware-endpoint": "^4.4.20", "@smithy/middleware-stack": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.15", "tslib": "^2.6.2" } }, "sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ=="],
"@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
"@smithy/url-parser": ["@smithy/url-parser@4.2.10", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A=="],
"@smithy/util-base64": ["@smithy/util-base64@4.3.1", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w=="],
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g=="],
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw=="],
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.1", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig=="],
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A=="],
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.36", "", { "dependencies": { "@smithy/property-provider": "^4.2.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-R0smq7EHQXRVMxkAxtH5akJ/FvgAmNF6bUy/GwY/N20T4GrwjT633NFm0VuRpC+8Bbv8R9A0DoJ9OiZL/M3xew=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.39", "", { "dependencies": { "@smithy/config-resolver": "^4.4.9", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-otWuoDm35btJV1L8MyHrPl462B07QCdMTktKc7/yM+Psv6KbED/ziXiHnmr7yPHUjfIwE9S8Max0LO24Mo3ZVg=="],
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg=="],
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA=="],
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA=="],
"@smithy/util-retry": ["@smithy/util-retry@4.2.10", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A=="],
"@smithy/util-stream": ["@smithy/util-stream@4.5.15", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.11", "@smithy/node-http-handler": "^4.4.12", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-hex-encoding": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw=="],
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.1", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g=="],
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.10", "", { "dependencies": { "@smithy/abort-controller": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-4eTWph/Lkg1wZEDAyObwme0kmhEb7J/JjibY2znJdrYRgKbKqB7YoEhhJVJ4R1g/SYih4zuwX7LpJaM8RsnTVg=="],
"@smithy/uuid": ["@smithy/uuid@1.1.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw=="],
"@spruceid/siwe-parser": ["@spruceid/siwe-parser@2.1.2", "", { "dependencies": { "@noble/hashes": "^1.1.2", "apg-js": "^4.3.0", "uri-js": "^4.4.1", "valid-url": "^1.0.9" } }, "sha512-d/r3S1LwJyMaRAKQ0awmo9whfXeE88Qt00vRj91q5uv5ATtWIQEGJ67Yr5eSZw5zp1/fZCXZYuEckt8lSkereQ=="],
"@stablelib/binary": ["@stablelib/binary@1.0.1", "", { "dependencies": { "@stablelib/int": "^1.0.1" } }, "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q=="],
"@stablelib/int": ["@stablelib/int@1.0.1", "", {}, "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w=="],
"@stablelib/random": ["@stablelib/random@1.0.2", "", { "dependencies": { "@stablelib/binary": "^1.0.1", "@stablelib/wipe": "^1.0.1" } }, "sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w=="],
"@stablelib/wipe": ["@stablelib/wipe@1.0.1", "", {}, "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg=="],
"@swc/core": ["@swc/core@1.15.8", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.8", "@swc/core-darwin-x64": "1.15.8", "@swc/core-linux-arm-gnueabihf": "1.15.8", "@swc/core-linux-arm64-gnu": "1.15.8", "@swc/core-linux-arm64-musl": "1.15.8", "@swc/core-linux-x64-gnu": "1.15.8", "@swc/core-linux-x64-musl": "1.15.8", "@swc/core-win32-arm64-msvc": "1.15.8", "@swc/core-win32-ia32-msvc": "1.15.8", "@swc/core-win32-x64-msvc": "1.15.8" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw=="], "@swc/core": ["@swc/core@1.15.8", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.8", "@swc/core-darwin-x64": "1.15.8", "@swc/core-linux-arm-gnueabihf": "1.15.8", "@swc/core-linux-arm64-gnu": "1.15.8", "@swc/core-linux-arm64-musl": "1.15.8", "@swc/core-linux-x64-gnu": "1.15.8", "@swc/core-linux-x64-musl": "1.15.8", "@swc/core-win32-arm64-msvc": "1.15.8", "@swc/core-win32-ia32-msvc": "1.15.8", "@swc/core-win32-x64-msvc": "1.15.8" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw=="],
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg=="], "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg=="],
@ -166,28 +419,122 @@
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/mailparser": ["@types/mailparser@3.4.6", "", { "dependencies": { "@types/node": "*", "iconv-lite": "^0.6.3" } }, "sha512-wVV3cnIKzxTffaPH8iRnddX1zahbYB1ZEoAxyhoBo3TBCBuK6nZ8M8JYO/RhsCuuBVOw/DEN/t/ENbruwlxn6Q=="],
"@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="], "@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="],
"@types/nodemailer": ["@types/nodemailer@6.4.22", "", { "dependencies": { "@types/node": "*" } }, "sha512-HV16KRsW7UyZBITE07B62k8PRAKFqRSFXn1T7vslurVjN761tMDBhk5Lbt17ehyTzK6XcyJnAgUpevrvkcVOzw=="], "@types/nodemailer": ["@types/nodemailer@6.4.22", "", { "dependencies": { "@types/node": "*" } }, "sha512-HV16KRsW7UyZBITE07B62k8PRAKFqRSFXn1T7vslurVjN761tMDBhk5Lbt17ehyTzK6XcyJnAgUpevrvkcVOzw=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@x402/core": ["@x402/core@2.5.0", "", { "dependencies": { "zod": "^3.24.2" } }, "sha512-nUr8HW8WhkU1DvrpUfsRvALy5NF8UWKoFezZOtX61mohxp2lWZpJ2GnvscxDM8nmBAbtIollmksd5z5pj8InXw=="],
"@x402/evm": ["@x402/evm@2.5.0", "", { "dependencies": { "@x402/core": "~2.5.0", "@x402/extensions": "~2.5.0", "viem": "^2.39.3", "zod": "^3.24.2" } }, "sha512-MBSTQZwLobMVcmYO7itOMJRkxfHstsDyr7F94o9Rk/Oinz0kjvCe4DFgZmFXyz3nQUgQFmDVgTK5KIzfYR5uIA=="],
"@x402/extensions": ["@x402/extensions@2.5.0", "", { "dependencies": { "@scure/base": "^1.2.6", "@x402/core": "~2.5.0", "ajv": "^8.17.1", "siwe": "^2.3.2", "tweetnacl": "^1.0.3", "viem": "^2.43.5", "zod": "^3.24.2" } }, "sha512-e7IQShbGUM/XQmzI8DQh2tX/k2XDUGI9DNF+ij2NHUyPEqAt5/mJCwOlaxS/60FWFdfFRfWjTsqaoS7Z8WLi+A=="],
"@zone-eu/mailsplit": ["@zone-eu/mailsplit@5.4.8", "", { "dependencies": { "libbase64": "1.3.0", "libmime": "5.3.7", "libqp": "2.1.1" } }, "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA=="],
"abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="],
"aes-js": ["aes-js@4.0.0-beta.5", "", {}, "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q=="],
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"apg-js": ["apg-js@4.4.0", "", {}, "sha512-fefmXFknJmtgtNEXfPwZKYkMFX4Fyeyz+fNF6JWp87biGOPslJbCBVU158zvKRZfHBKnJDy8CMM40oLFGkXT8Q=="],
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"encoding-japanese": ["encoding-japanese@2.2.0", "", {}, "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"ethers": ["ethers@6.16.0", "", { "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", "@types/node": "22.7.5", "aes-js": "4.0.0-beta.5", "tslib": "2.7.0", "ws": "8.17.1" } }, "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A=="],
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="], "hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="],
"html-to-text": ["html-to-text@9.0.5", "", { "dependencies": { "@selderee/plugin-htmlparser2": "^0.11.0", "deepmerge": "^4.3.1", "dom-serializer": "^2.0.0", "htmlparser2": "^8.0.2", "selderee": "^0.11.0" } }, "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg=="],
"htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="],
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"imapflow": ["imapflow@1.2.10", "", { "dependencies": { "@zone-eu/mailsplit": "5.4.8", "encoding-japanese": "2.2.0", "iconv-lite": "0.7.2", "libbase64": "1.3.0", "libmime": "5.3.7", "libqp": "2.1.1", "nodemailer": "8.0.1", "pino": "10.3.1", "socks": "2.8.7" } }, "sha512-tqmk0Gj4YBEnGCjjrUYWIf3Z4tzn4iihUcMkBRbafvHF3LqEiYNCSJAAYYbwERFxlikOJ3zzqtEcoxCUTjMv2Q=="],
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
"is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
"isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="],
"libbase64": ["libbase64@1.3.0", "", {}, "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg=="],
"libmime": ["libmime@5.3.7", "", { "dependencies": { "encoding-japanese": "2.2.0", "iconv-lite": "0.6.3", "libbase64": "1.3.0", "libqp": "2.1.1" } }, "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw=="],
"libqp": ["libqp@2.1.1", "", {}, "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow=="],
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
"mailparser": ["mailparser@3.9.3", "", { "dependencies": { "@zone-eu/mailsplit": "5.4.8", "encoding-japanese": "2.2.0", "he": "1.2.0", "html-to-text": "9.0.5", "iconv-lite": "0.7.2", "libmime": "5.3.7", "linkify-it": "5.0.0", "nodemailer": "7.0.13", "punycode.js": "2.3.1", "tlds": "1.261.0" } }, "sha512-AnB0a3zROum6fLaa52L+/K2SoRJVyFDk78Ea6q1D0ofcZLxWEWDtsS1+OrVqKbV7r5dulKL/AwYQccFGAPpuYQ=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"nodemailer": ["nodemailer@6.10.1", "", {}, "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA=="], "nodemailer": ["nodemailer@6.10.1", "", {}, "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA=="],
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
"ox": ["ox@0.12.4", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q=="],
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
"perfect-arrows": ["perfect-arrows@0.3.7", "", {}, "sha512-wEN2gerTPVWl3yqoFEF8OeGbg3aRe2sxNUi9rnyYrCsL4JcI6K2tBDezRtqVrYG0BNtsWLdYiiTrYm+X//8yLQ=="], "perfect-arrows": ["perfect-arrows@0.3.7", "", {}, "sha512-wEN2gerTPVWl3yqoFEF8OeGbg3aRe2sxNUi9rnyYrCsL4JcI6K2tBDezRtqVrYG0BNtsWLdYiiTrYm+X//8yLQ=="],
"perfect-freehand": ["perfect-freehand@1.2.2", "", {}, "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ=="], "perfect-freehand": ["perfect-freehand@1.2.2", "", {}, "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ=="],
@ -196,28 +543,146 @@
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="],
"pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
"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=="], "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": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="], "postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="],
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
"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=="], "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=="],
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
"siwe": ["siwe@2.3.2", "", { "dependencies": { "@spruceid/siwe-parser": "^2.1.2", "@stablelib/random": "^1.0.1", "uri-js": "^4.4.1", "valid-url": "^1.0.9" }, "peerDependencies": { "ethers": "^5.6.8 || ^6.0.8" } }, "sha512-aSf+6+Latyttbj5nMu6GF3doMfv2UYj83hhwZgUF20ky6fTS83uVhkQABdIVnEuS8y1bBdk7p6ltb9SmlhTTlA=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
"thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
"valid-url": ["valid-url@1.0.9", "", {}, "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA=="],
"viem": ["viem@2.46.3", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.12.4", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-2LJS+Hyh2sYjHXQtzfv1kU9pZx9dxFzvoU/ZKIcn0FNtOU0HQuIICuYdWtUDFHaGXbAdVo8J1eCvmjkL9JVGwg=="],
"vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
"vite-plugin-top-level-await": ["vite-plugin-top-level-await@1.6.0", "", { "dependencies": { "@rollup/plugin-virtual": "^3.0.2", "@swc/core": "^1.12.14", "@swc/wasm": "^1.12.14", "uuid": "10.0.0" }, "peerDependencies": { "vite": ">=2.8" } }, "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww=="], "vite-plugin-top-level-await": ["vite-plugin-top-level-await@1.6.0", "", { "dependencies": { "@rollup/plugin-virtual": "^3.0.2", "@swc/core": "^1.12.14", "@swc/wasm": "^1.12.14", "uuid": "10.0.0" }, "peerDependencies": { "vite": ">=2.8" } }, "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww=="],
"vite-plugin-wasm": ["vite-plugin-wasm@3.5.0", "", { "peerDependencies": { "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" } }, "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ=="], "vite-plugin-wasm": ["vite-plugin-wasm@3.5.0", "", { "peerDependencies": { "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" } }, "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@automerge/automerge/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "@automerge/automerge/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@scure/bip32/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
"@scure/bip32/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@scure/bip39/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@spruceid/siwe-parser/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"ethers/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.10.1", "", {}, "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw=="],
"ethers/@noble/curves": ["@noble/curves@1.2.0", "", { "dependencies": { "@noble/hashes": "1.3.2" } }, "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw=="],
"ethers/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="],
"ethers/@types/node": ["@types/node@22.7.5", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ=="],
"ethers/tslib": ["tslib@2.7.0", "", {}, "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="],
"ethers/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"imapflow/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"imapflow/nodemailer": ["nodemailer@8.0.1", "", {}, "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg=="],
"mailparser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"mailparser/nodemailer": ["nodemailer@7.0.13", "", {}, "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw=="],
"ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
"ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
"viem/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"ethers/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
} }
} }

View File

@ -54,12 +54,12 @@ export class FolkBookShelf extends HTMLElement {
private loadDemoBooks() { private loadDemoBooks() {
this.books = [ this.books = [
{ id: "b1", slug: "governing-the-commons", title: "Governing the Commons", author: "Elinor Ostrom", description: "Analysis of collective action and the governance of common-pool resources", pdf_size_bytes: 2457600, page_count: 280, tags: ["economics", "governance"], cover_color: "#2563eb", contributor_name: "Community Library", featured: true, view_count: 342, created_at: "2026-01-15" }, { id: "b1", slug: "amusing-ourselves-to-death", title: "Amusing Ourselves to Death", author: "Neil Postman", description: "A prophetic look at what happens when politics, journalism, education, and even religion become subject to the demands of entertainment. Originally published in 1985, this book remains incredibly relevant in the age of social media and constant digital distraction.", pdf_size_bytes: 2457600, page_count: 231, tags: ["media criticism", "society", "technology", "culture"], cover_color: "#1e3a5f", contributor_name: null, featured: true, view_count: 523, created_at: "2025-11-10" },
{ id: "b2", slug: "the-mushroom-at-the-end-of-the-world", title: "The Mushroom at the End of the World", author: "Anna Lowenhaupt Tsing", description: "On the possibility of life in capitalist ruins", pdf_size_bytes: 3145728, page_count: 352, tags: ["ecology", "anthropology"], cover_color: "#059669", contributor_name: null, featured: false, view_count: 187, created_at: "2026-01-20" }, { id: "b2", slug: "interference", title: "Interference: A Grand Scientific Musical Theory", author: "Richard Merrick", description: "A groundbreaking exploration of Harmonic Interference Theory - a set of principles explaining music perception using the physics of harmonic standing waves, connecting music theory to cymatics, Fibonacci sequences, and the fundamental patterns found throughout nature.", pdf_size_bytes: 8388608, page_count: 524, tags: ["music theory", "harmonics", "cymatics", "science", "perception"], cover_color: "#7c3aed", contributor_name: null, featured: true, view_count: 312, created_at: "2025-12-05" },
{ id: "b3", slug: "doughnut-economics", title: "Doughnut Economics", author: "Kate Raworth", description: "Seven ways to think like a 21st-century economist", pdf_size_bytes: 1887436, page_count: 320, tags: ["economics"], cover_color: "#d97706", contributor_name: "Reading Circle", featured: true, view_count: 256, created_at: "2026-02-01" }, { id: "b3", slug: "governing-the-commons", title: "Governing the Commons", author: "Elinor Ostrom", description: "Analysis of collective action and the governance of common-pool resources", pdf_size_bytes: 2457600, page_count: 280, tags: ["economics", "governance"], cover_color: "#2563eb", contributor_name: "Community Library", featured: true, view_count: 342, created_at: "2026-01-15" },
{ id: "b4", slug: "patterns-of-commoning", title: "Patterns of Commoning", author: "David Bollier & Silke Helfrich", description: "A collection of essays on commons-based peer production", pdf_size_bytes: 4194304, page_count: 416, tags: ["commons", "governance"], cover_color: "#7c3aed", contributor_name: null, featured: false, view_count: 98, created_at: "2026-02-05" }, { id: "b4", slug: "the-mushroom-at-the-end-of-the-world", title: "The Mushroom at the End of the World", author: "Anna Lowenhaupt Tsing", description: "On the possibility of life in capitalist ruins", pdf_size_bytes: 3145728, page_count: 352, tags: ["ecology", "anthropology"], cover_color: "#059669", contributor_name: null, featured: false, view_count: 187, created_at: "2026-01-20" },
{ id: "b5", slug: "entangled-life", title: "Entangled Life", author: "Merlin Sheldrake", description: "How fungi make our worlds, change our minds, and shape our futures", pdf_size_bytes: 2621440, page_count: 368, tags: ["ecology", "science"], cover_color: "#0891b2", contributor_name: "Mycofi Lab", featured: false, view_count: 431, created_at: "2026-02-10" }, { id: "b5", slug: "doughnut-economics", title: "Doughnut Economics", author: "Kate Raworth", description: "Seven ways to think like a 21st-century economist", pdf_size_bytes: 1887436, page_count: 320, tags: ["economics"], cover_color: "#d97706", contributor_name: "Reading Circle", featured: false, view_count: 256, created_at: "2026-02-01" },
{ id: "b6", slug: "free-fair-and-alive", title: "Free, Fair, and Alive", author: "David Bollier & Silke Helfrich", description: "The insurgent power of the commons", pdf_size_bytes: 3670016, page_count: 340, tags: ["commons", "politics"], cover_color: "#e11d48", contributor_name: null, featured: true, view_count: 175, created_at: "2026-02-12" } { id: "b6", slug: "entangled-life", title: "Entangled Life", author: "Merlin Sheldrake", description: "How fungi make our worlds, change our minds, and shape our futures", pdf_size_bytes: 2621440, page_count: 368, tags: ["ecology", "science"], cover_color: "#0891b2", contributor_name: "Mycofi Lab", featured: false, view_count: 431, created_at: "2026-02-10" },
]; ];
} }

View File

@ -35,29 +35,61 @@ class FolkCalendarView extends HTMLElement {
const month = now.getMonth(); const month = now.getMonth();
const sources = [ const sources = [
{ name: "rSpace Team", color: "#6366f1" }, { name: "Work (Google Calendar)", color: "#3b82f6" },
{ name: "Community", color: "#22c55e" }, { name: "Travel (Manual)", color: "#f97316" },
{ name: "Personal", color: "#f59e0b" }, { name: "Personal (ICS)", color: "#10b981" },
{ name: "Conferences (Manual)", color: "#8b5cf6" },
]; ];
const demoEvents = [ // Dense workday calendar across multiple cities
{ day: 3, title: "Sprint Planning", source: 0, desc: "Plan the next two-week sprint", location: "Room A", virtual: false }, const demoEvents: { day: number; title: string; source: number; desc: string; location: string | null; virtual: boolean; startH: number; startM: number; durationMin: number }[] = [
{ day: 5, title: "Community Call", source: 1, desc: "Open community discussion", location: null, virtual: true }, // --- Berlin Work Block (Days 3-10) ---
{ day: 8, title: "Design Review", source: 0, desc: "Review new component designs", location: "Design Lab", virtual: false }, { day: 3, title: "Team Standup", source: 0, desc: "Daily sync — Berlin engineering team", location: "Factory Berlin, Rheinsberger Str. 76/77", virtual: false, startH: 9, startM: 30, durationMin: 30 },
{ day: 10, title: "Standup", source: 0, desc: "Daily sync — async-first format", location: null, virtual: true }, { day: 3, title: "Code Review Session", source: 0, desc: "Review PRs from the weekend batch", location: "Factory Berlin", virtual: false, startH: 14, startM: 0, durationMin: 60 },
{ day: 14, title: "Hackathon Day 1", source: 1, desc: "Build something local-first in 48h", location: "Hackerspace", virtual: false }, { day: 5, title: "Product Review", source: 0, desc: "Quarterly product roadmap review with stakeholders", location: "Factory Berlin, Conference Room 3", virtual: false, startH: 10, startM: 0, durationMin: 90 },
{ day: 15, title: "Hackathon Day 2", source: 1, desc: "Demos and judging", location: "Hackerspace", virtual: false }, { day: 5, title: "Lunch with Alex", source: 2, desc: "Catch up over Vietnamese food", location: "District Mot, Rosenthaler Str. 62, Berlin", virtual: false, startH: 12, startM: 30, durationMin: 60 },
{ day: 18, title: "Release Planning", source: 0, desc: "Scope the next release milestone", location: null, virtual: true }, { day: 7, title: "Sprint Planning", source: 0, desc: "Plan sprint 24 — local-first sync features", location: "Factory Berlin, Conference Room 1", virtual: false, startH: 10, startM: 0, durationMin: 120 },
{ day: 20, title: "Town Hall", source: 1, desc: "Monthly all-hands update", location: null, virtual: true }, { day: 7, title: "Architecture Deep-Dive", source: 0, desc: "CRDT merge strategy for offline-first mobile", location: "Factory Berlin", virtual: false, startH: 15, startM: 0, durationMin: 90 },
{ day: 22, title: "Retrospective", source: 0, desc: "Reflect on what went well and what to improve", location: "Room B", virtual: false }, { day: 8, title: "Client Call NYC", source: 0, desc: "Sync with NYC partner team on API integration", location: null, virtual: true, startH: 16, startM: 0, durationMin: 60 },
{ day: 25, title: "Workshop: Local-First", source: 1, desc: "Hands-on local-first architecture workshop", location: "Community Center", virtual: false }, { day: 10, title: "1:1 with Manager", source: 0, desc: "Monthly check-in — career growth, project scope", location: "Factory Berlin, Phone Booth 4", virtual: false, startH: 11, startM: 0, durationMin: 45 },
{ day: 27, title: "Demo Day", source: 0, desc: "Show off what shipped this month", location: null, virtual: true }, { day: 10, title: "Deploy Prep", source: 0, desc: "Pre-release checklist and staging verification", location: null, virtual: true, startH: 15, startM: 30, durationMin: 60 },
{ day: 28, title: "Social Meetup", source: 2, desc: "Casual evening hangout", location: "Cafe Decentralized", virtual: false },
// --- Travel: Berlin to Amsterdam (Days 12-14) ---
{ day: 12, title: "Train Berlin \u2192 Amsterdam", source: 1, desc: "ICE 643 Berlin Hbf \u2192 Amsterdam Centraal, Seat 64 Window", location: "Berlin Hauptbahnhof, Platform 11", virtual: false, startH: 7, startM: 15, durationMin: 390 },
{ day: 12, title: "Hotel Check-in", source: 1, desc: "Hotel V Nesplein, Nes 49, Amsterdam", location: "Hotel V Nesplein, Amsterdam", virtual: false, startH: 14, startM: 30, durationMin: 30 },
{ day: 13, title: "Partner Meeting", source: 0, desc: "On-site collaboration with Amsterdam design team", location: "WeWork Weteringschans, Amsterdam", virtual: false, startH: 10, startM: 0, durationMin: 180 },
{ day: 13, title: "Canal District Walk", source: 2, desc: "Afternoon stroll along Prinsengracht and Jordaan", location: "Prinsengracht, Amsterdam", virtual: false, startH: 15, startM: 0, durationMin: 120 },
{ day: 13, title: "Dinner at Moeders", source: 2, desc: "Traditional Dutch dinner — try the stamppot", location: "Restaurant Moeders, Rozengracht 251, Amsterdam", virtual: false, startH: 19, startM: 0, durationMin: 90 },
{ day: 14, title: "Return Train Amsterdam \u2192 Berlin", source: 1, desc: "ICE 148 Amsterdam Centraal \u2192 Berlin Hbf, Seat 38 Aisle", location: "Amsterdam Centraal, Platform 7b", virtual: false, startH: 9, startM: 30, durationMin: 390 },
// --- Personal (Days 15-20) ---
{ day: 15, title: "Dinner with Friends", source: 2, desc: "Birthday dinner for Mia at the Italian place", location: "Il Casolare, Grimmstr. 30, Berlin", virtual: false, startH: 19, startM: 30, durationMin: 120 },
{ day: 16, title: "Grocery Run", source: 2, desc: "Weekly groceries at the Saturday market", location: "Maybachufer Market, Berlin", virtual: false, startH: 10, startM: 0, durationMin: 60 },
{ day: 17, title: "Weekend Hike", source: 2, desc: "Grunewald forest loop trail, ~14 km", location: "S-Bahn Grunewald, Berlin", virtual: false, startH: 8, startM: 0, durationMin: 300 },
{ day: 19, title: "Dentist Appointment", source: 2, desc: "Regular checkup, Dr. Weber", location: "Zahnarzt Weber, Torstr. 140, Berlin", virtual: false, startH: 9, startM: 0, durationMin: 45 },
{ day: 20, title: "Yoga Class", source: 2, desc: "Vinyasa flow — bring own mat", location: "Yoga Studio Kreuzberg, Oranienstr. 25, Berlin", virtual: false, startH: 7, startM: 30, durationMin: 75 },
{ day: 20, title: "Book Club", source: 2, desc: "Discussing \"The Mushroom at the End of the World\" by Anna Tsing", location: "Shakespeare & Sons, Warschauer Str. 74, Berlin", virtual: false, startH: 19, startM: 0, durationMin: 90 },
// --- Work Wrap-up (Days 21-22) ---
{ day: 21, title: "Sprint Retro", source: 0, desc: "Sprint 23 retrospective — what worked, what didn't", location: "Factory Berlin, Conference Room 2", virtual: false, startH: 10, startM: 0, durationMin: 60 },
{ day: 21, title: "Release Deploy", source: 0, desc: "Push v2.4.0 to production, monitor metrics", location: null, virtual: true, startH: 14, startM: 0, durationMin: 120 },
{ day: 22, title: "Demo Day", source: 0, desc: "Sprint 23 showcase — live demos for stakeholders and community", location: "Factory Berlin, Main Hall", virtual: false, startH: 14, startM: 0, durationMin: 90 },
{ day: 22, title: "Team Drinks", source: 2, desc: "Celebrate the release with the team", location: "Prater Garten, Kastanienallee 7-9, Berlin", virtual: false, startH: 17, startM: 30, durationMin: 120 },
// --- Conference: Web Summit (Days 24-27, Lisbon) ---
{ day: 24, title: "Flight Berlin \u2192 Lisbon", source: 1, desc: "TAP TP 571 BER \u2192 LIS, Gate B22", location: "BER Airport, Berlin", virtual: false, startH: 6, startM: 45, durationMin: 195 },
{ day: 24, title: "Hotel Check-in Lisbon", source: 1, desc: "Hotel da Baixa, Rua da Prata 242, Lisbon", location: "Hotel da Baixa, Lisbon", virtual: false, startH: 13, startM: 0, durationMin: 30 },
{ day: 25, title: "Web Summit Day 1", source: 3, desc: "Opening keynotes, startup pavilion, networking. Main stage + Centre Stage tracks.", location: "Altice Arena / FIL, Lisbon", virtual: false, startH: 9, startM: 0, durationMin: 540 },
{ day: 25, title: "Speaker Dinner", source: 3, desc: "Invited speakers dinner at the riverside venue", location: "Ponto Final, Cacilhas, Lisbon", virtual: false, startH: 20, startM: 0, durationMin: 120 },
{ day: 26, title: "Web Summit Day 2", source: 3, desc: "Panel: \"Local-First Software & the Future of Collaboration\". Our talk at 14:00 on Centre Stage.", location: "Altice Arena / FIL, Lisbon", virtual: false, startH: 9, startM: 0, durationMin: 540 },
{ day: 26, title: "Networking Happy Hour", source: 3, desc: "Post-conference drinks with open-source community", location: "Time Out Market, Lisbon", virtual: false, startH: 18, startM: 30, durationMin: 120 },
{ day: 27, title: "Lisbon City Tour", source: 2, desc: "Alfama neighborhood, Tram 28, Pasteis de Belem, Praca do Comercio", location: "Alfama, Lisbon", virtual: false, startH: 10, startM: 0, durationMin: 360 },
{ day: 27, title: "Flight Lisbon \u2192 Berlin", source: 1, desc: "TAP TP 572 LIS \u2192 BER, Gate 41", location: "Lisbon Humberto Delgado Airport", virtual: false, startH: 19, startM: 30, durationMin: 195 },
]; ];
this.events = demoEvents.map((e, i) => { this.events = demoEvents.map((e, i) => {
const startDate = new Date(year, month, e.day, 10 + (i % 4), 0); const startDate = new Date(year, month, e.day, e.startH, e.startM);
const endDate = new Date(startDate.getTime() + 3600000); const endDate = new Date(startDate.getTime() + e.durationMin * 60000);
const src = sources[e.source]; const src = sources[e.source];
return { return {
id: `demo-${i + 1}`, id: `demo-${i + 1}`,

View File

@ -14,8 +14,9 @@ class FolkChoicesDashboard extends HTMLElement {
private hoveredPerson: string | null = null; private hoveredPerson: string | null = null;
private rankItems: { id: number; name: string; emoji: string }[] = []; private rankItems: { id: number; name: string; emoji: string }[] = [];
private rankDragging: number | null = null; private rankDragging: number | null = null;
private voteOptions: { id: number; name: string; emoji: string; votes: number }[] = []; private voteOptions: { id: string; name: string; color: string; votes: number }[] = [];
private voted = false; private voted = false;
private votedId: string | null = null;
private simTimer: number | null = null; private simTimer: number | null = null;
constructor() { constructor() {
@ -140,19 +141,20 @@ class FolkChoicesDashboard extends HTMLElement {
private loadDemoData() { private loadDemoData() {
this.rankItems = [ this.rankItems = [
{ id: 1, name: "Thai", emoji: "🍜" }, { id: 1, name: "Thai Place", emoji: "🍜" },
{ id: 2, name: "Pizza", emoji: "🍕" }, { id: 2, name: "Pizza", emoji: "🍕" },
{ id: 3, name: "Sushi", emoji: "🍣" }, { id: 3, name: "Sushi Bar", emoji: "🍣" },
{ id: 4, name: "Tacos", emoji: "🌮" }, { id: 4, name: "Tacos", emoji: "🌮" },
{ id: 5, name: "Burgers", emoji: "🍔" }, { id: 5, name: "Burgers", emoji: "🍔" },
]; ];
this.voteOptions = [ this.voteOptions = [
{ id: 1, name: "Inception", emoji: "🌀", votes: 4 }, { id: "action", name: "Action Movie", color: "#ef4444", votes: 2 },
{ id: 2, name: "Spirited Away", emoji: "🐉", votes: 3 }, { id: "comedy", name: "Comedy", color: "#f59e0b", votes: 3 },
{ id: 3, name: "The Matrix", emoji: "💊", votes: 5 }, { id: "horror", name: "Horror", color: "#8b5cf6", votes: 1 },
{ id: 4, name: "Parasite", emoji: "🪨", votes: 2 }, { id: "scifi", name: "Sci-Fi", color: "#06b6d4", votes: 2 },
]; ];
this.voted = false; this.voted = false;
this.votedId = null;
this.startVoteSim(); this.startVoteSim();
this.renderDemo(); this.renderDemo();
} }
@ -162,7 +164,7 @@ class FolkChoicesDashboard extends HTMLElement {
const tick = () => { const tick = () => {
if (this.voted) return; if (this.voted) return;
const idx = Math.floor(Math.random() * this.voteOptions.length); const idx = Math.floor(Math.random() * this.voteOptions.length);
this.voteOptions[idx].votes += 1 + Math.floor(Math.random() * 3); this.voteOptions[idx].votes += 1;
if (this.demoTab === "voting") this.renderDemo(); if (this.demoTab === "voting") this.renderDemo();
}; };
const scheduleNext = () => { const scheduleNext = () => {
@ -217,9 +219,9 @@ class FolkChoicesDashboard extends HTMLElement {
.rank-item.dragging { opacity: 0.4; transform: scale(0.97); } .rank-item.dragging { opacity: 0.4; transform: scale(0.97); }
.rank-item.drag-over { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99,102,241,0.3); } .rank-item.drag-over { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99,102,241,0.3); }
.rank-pos { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.8rem; color: #0f172a; flex-shrink: 0; } .rank-pos { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.8rem; color: #0f172a; flex-shrink: 0; }
.rank-pos.gold { background: #fbbf24; } .rank-pos.gold { background: #f59e0b; }
.rank-pos.silver { background: #94a3b8; } .rank-pos.silver { background: #94a3b8; }
.rank-pos.bronze { background: #d97706; } .rank-pos.bronze { background: #cd7f32; }
.rank-pos.plain { background: #334155; color: #94a3b8; } .rank-pos.plain { background: #334155; color: #94a3b8; }
.rank-emoji { font-size: 1.5rem; flex-shrink: 0; } .rank-emoji { font-size: 1.5rem; flex-shrink: 0; }
.rank-name { flex: 1; color: #f1f5f9; font-weight: 600; font-size: 1rem; } .rank-name { flex: 1; color: #f1f5f9; font-weight: 600; font-size: 1rem; }
@ -230,11 +232,12 @@ class FolkChoicesDashboard extends HTMLElement {
.vote-option { display: flex; align-items: center; gap: 12px; padding: 0.75rem 1rem; margin-bottom: 8px; background: #1e293b; border: 1px solid #334155; border-radius: 10px; cursor: pointer; position: relative; overflow: hidden; transition: border-color 0.15s; } .vote-option { display: flex; align-items: center; gap: 12px; padding: 0.75rem 1rem; margin-bottom: 8px; background: #1e293b; border: 1px solid #334155; border-radius: 10px; cursor: pointer; position: relative; overflow: hidden; transition: border-color 0.15s; }
.vote-option:hover { border-color: #6366f1; } .vote-option:hover { border-color: #6366f1; }
.vote-option.voted { border-color: #6366f1; } .vote-option.voted { border-color: #6366f1; }
.vote-fill { position: absolute; left: 0; top: 0; bottom: 0; background: rgba(99,102,241,0.12); transition: width 0.4s ease; pointer-events: none; } .vote-fill { position: absolute; left: 0; top: 0; bottom: 0; opacity: 0.12; transition: width 0.7s ease-out; pointer-events: none; }
.vote-emoji { font-size: 1.5rem; flex-shrink: 0; position: relative; z-index: 1; } .vote-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; position: relative; z-index: 1; }
.vote-name { flex: 1; color: #f1f5f9; font-weight: 600; font-size: 1rem; position: relative; z-index: 1; } .vote-name { flex: 1; color: #f1f5f9; font-weight: 600; font-size: 1rem; position: relative; z-index: 1; }
.vote-count { color: #818cf8; font-weight: 700; font-size: 1rem; min-width: 36px; text-align: right; position: relative; z-index: 1; } .vote-count { color: #94a3b8; font-weight: 400; font-size: 0.8rem; min-width: 24px; text-align: right; position: relative; z-index: 1; font-variant-numeric: tabular-nums; }
.vote-badge { font-size: 0.65rem; padding: 2px 7px; border-radius: 999px; background: #6366f1; color: #fff; margin-left: 6px; position: relative; z-index: 1; font-weight: 500; } .vote-pct { font-weight: 600; font-size: 0.8rem; min-width: 40px; text-align: right; position: relative; z-index: 1; font-variant-numeric: tabular-nums; }
.vote-badge { font-size: 0.625rem; padding: 2px 6px; border-radius: 999px; background: rgba(255,255,255,0.05); color: #94a3b8; margin-left: 6px; position: relative; z-index: 1; font-weight: 400; }
.vote-actions { display: flex; justify-content: center; margin-top: 1rem; } .vote-actions { display: flex; justify-content: center; margin-top: 1rem; }
.vote-reset { padding: 0.5rem 1.25rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 0.875rem; font-family: inherit; transition: all 0.15s; } .vote-reset { padding: 0.5rem 1.25rem; border-radius: 8px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 0.875rem; font-family: inherit; transition: all 0.15s; }
.vote-reset:hover { border-color: #ef4444; color: #fca5a5; } .vote-reset:hover { border-color: #ef4444; color: #fca5a5; }
@ -269,9 +272,9 @@ class FolkChoicesDashboard extends HTMLElement {
const cx = 200, cy = 200, maxR = 150; const cx = 200, cy = 200, maxR = 150;
const axes = ["Taste", "Price", "Speed", "Healthy", "Distance"]; const axes = ["Taste", "Price", "Speed", "Healthy", "Distance"];
const people: { name: string; color: string; values: number[] }[] = [ const people: { name: string; color: string; values: number[] }[] = [
{ name: "Alice", color: "#f472b6", values: [0.9, 0.5, 0.7, 0.8, 0.4] }, { name: "Alice", color: "#7c5bf5", values: [0.9, 0.6, 0.8, 0.4, 0.7] },
{ name: "Bob", color: "#38bdf8", values: [0.6, 0.8, 0.5, 0.4, 0.9] }, { name: "Bob", color: "#f59e0b", values: [0.5, 0.9, 0.6, 0.7, 0.8] },
{ name: "Carol", color: "#a3e635", values: [0.7, 0.6, 0.9, 0.7, 0.6] }, { name: "Carol", color: "#10b981", values: [0.7, 0.4, 0.9, 0.8, 0.3] },
]; ];
const angleStep = 360 / axes.length; const angleStep = 360 / axes.length;
@ -351,30 +354,29 @@ class FolkChoicesDashboard extends HTMLElement {
private renderVoting(): string { private renderVoting(): string {
const sorted = [...this.voteOptions].sort((a, b) => b.votes - a.votes); const sorted = [...this.voteOptions].sort((a, b) => b.votes - a.votes);
const total = sorted.reduce((s, o) => s + o.votes, 0);
const maxVotes = Math.max(...sorted.map((o) => o.votes), 1); const maxVotes = Math.max(...sorted.map((o) => o.votes), 1);
const leaderId = sorted[0]?.id;
const items = sorted.map((opt) => { const items = sorted.map((opt) => {
const pct = (opt.votes / maxVotes) * 100; const pct = total > 0 ? (opt.votes / total) * 100 : 0;
const isLeader = opt.id === leaderId; const isLeader = opt.votes === maxVotes && total > 4;
return `<div class="vote-option${this.voted ? " voted" : ""}" data-vote-id="${opt.id}"> return `<div class="vote-option${this.voted ? " voted" : ""}" data-vote-id="${opt.id}" style="border-color:${this.voted === true ? (this.votedId === opt.id ? opt.color : '#334155') : '#334155'}">
<div class="vote-fill" style="width:${pct}%"></div> <div class="vote-fill" style="width:${pct}%;background:${opt.color}"></div>
<span class="vote-emoji">${opt.emoji}</span> <span class="vote-dot" style="background:${opt.color}"></span>
<span class="vote-name">${this.esc(opt.name)}${isLeader ? `<span class="vote-badge">Leading</span>` : ""}</span> <span class="vote-name">${this.esc(opt.name)}${isLeader ? `<span class="vote-badge">leading</span>` : ""}</span>
<span class="vote-count">${opt.votes}</span> <span class="vote-count">${opt.votes}</span>
<span class="vote-pct" style="color:${opt.color}">${pct.toFixed(0)}%</span>
</div>`; </div>`;
}).join(""); }).join("");
const status = this.voted const status = this.voted
? "You voted! Simulation paused." ? "Results are in!"
: "Votes are rolling in live..."; : "Pick a movie \u2014 votes update live";
return `<div class="vote-wrap"> return `<div class="vote-wrap">
<div class="vote-status">${status}</div> <div class="vote-status">${status}</div>
${items} ${items}
<div class="vote-actions"> ${this.voted ? `<div class="vote-actions"><button class="vote-reset">Reset demo</button></div>` : ""}
<button class="vote-reset">Reset</button>
</div>
</div>`; </div>`;
} }
@ -455,11 +457,12 @@ class FolkChoicesDashboard extends HTMLElement {
this.shadow.querySelectorAll<HTMLElement>(".vote-option").forEach((el) => { this.shadow.querySelectorAll<HTMLElement>(".vote-option").forEach((el) => {
el.addEventListener("click", () => { el.addEventListener("click", () => {
if (this.voted) return; if (this.voted) return;
const id = parseInt(el.dataset.voteId || "0", 10); const id = el.dataset.voteId || "";
const opt = this.voteOptions.find((o) => o.id === id); const opt = this.voteOptions.find((o) => o.id === id);
if (opt) { if (opt) {
opt.votes += 1; opt.votes += 1;
this.voted = true; this.voted = true;
this.votedId = id;
this.renderDemo(); this.renderDemo();
} }
}); });
@ -469,8 +472,14 @@ class FolkChoicesDashboard extends HTMLElement {
const resetBtn = this.shadow.querySelector<HTMLButtonElement>(".vote-reset"); const resetBtn = this.shadow.querySelector<HTMLButtonElement>(".vote-reset");
if (resetBtn) { if (resetBtn) {
resetBtn.addEventListener("click", () => { resetBtn.addEventListener("click", () => {
this.voteOptions.forEach((o) => (o.votes = Math.floor(Math.random() * 5) + 1)); this.voteOptions = [
{ id: "action", name: "Action Movie", color: "#ef4444", votes: 2 },
{ id: "comedy", name: "Comedy", color: "#f59e0b", votes: 3 },
{ id: "horror", name: "Horror", color: "#8b5cf6", votes: 1 },
{ id: "scifi", name: "Sci-Fi", color: "#06b6d4", votes: 2 },
];
this.voted = false; this.voted = false;
this.votedId = null;
this.startVoteSim(); this.startVoteSim();
this.renderDemo(); this.renderDemo();
}); });

View File

@ -42,8 +42,8 @@ class FolkInboxClient extends HTMLElement {
private loadDemoData() { private loadDemoData() {
this.mailboxes = [ this.mailboxes = [
{ slug: "team", name: "Team Inbox", email: "team@rspace.online", description: "Internal team communications" }, { slug: "team", name: "Team Inbox", email: "team@rspace.online", description: "Shared workspace inbox for internal team communications" },
{ slug: "support", name: "Support", email: "support@rspace.online", description: "User support requests" }, { slug: "support", name: "Support", email: "support@rspace.online", description: "Community support requests with multi-sig approval workflows" },
]; ];
this.render(); this.render();
} }

View File

@ -1,8 +1,12 @@
/** /**
* <folk-map-viewer> real-time location sharing map. * <folk-map-viewer> -- real-time location sharing map.
* *
* Creates/joins map rooms, shows participant locations on a map, * Creates/joins map rooms, shows participant locations on a map,
* and provides location sharing controls. * and provides location sharing controls.
*
* Demo mode: shows 6 cosmolocal print providers on a world map with
* connection arcs, interactive hover tooltips, and a feature summary
* matching the standalone rMaps capabilities.
*/ */
class FolkMapViewer extends HTMLElement { class FolkMapViewer extends HTMLElement {
@ -51,28 +55,70 @@ class FolkMapViewer extends HTMLElement {
} }
private renderDemo() { private renderDemo() {
const mapWidth = 800; const W = 900;
const mapHeight = 400; const H = 460;
const projectX = (lng: number) => ((lng + 180) * (mapWidth / 360)); const px = (lng: number) => ((lng + 180) / 360) * W;
const projectY = (lat: number) => ((90 - lat) * (mapHeight / 180)); const py = (lat: number) => ((90 - lat) / 180) * H;
const providerDots = this.providers.map((p) => { // Label offsets to avoid overlapping (Pittsburgh/Toronto are close)
const x = projectX(p.lng); const labelOffsets: Record<string, [number, number]> = {
const y = projectY(p.lat); "Radiant Hall Press": [10, -8],
const labelX = x + 10; "Tiny Splendor": [-110, 14],
const labelY = y + 4; "People's Print Shop": [10, 14],
"Colour Code Press": [10, -8],
"Druckwerkstatt Berlin": [10, 14],
"Kink\u014D Printing Collective": [-150, -8],
};
// Connection arcs between providers (great-circle style curves)
const connections = [
[0, 2], // Pittsburgh -- Toronto
[0, 3], // Pittsburgh -- London
[3, 4], // London -- Berlin
[4, 5], // Berlin -- Tokyo
[1, 5], // LA -- Tokyo (Pacific)
[0, 1], // Pittsburgh -- LA
];
const arcs = connections.map(([i, j]) => {
const a = this.providers[i];
const b = this.providers[j];
const x1 = px(a.lng), y1 = py(a.lat);
const x2 = px(b.lng), y2 = py(b.lat);
// Curved midpoint -- arc above the straight line
const mx = (x1 + x2) / 2;
const my = (y1 + y2) / 2 - Math.abs(x2 - x1) * 0.12;
return `<path d="M${x1},${y1} Q${mx},${my} ${x2},${y2}" fill="none" stroke="rgba(148,163,184,0.15)" stroke-width="1" stroke-dasharray="4,3" />`;
}).join("\n");
// Provider pins (drop-pin style) and labels
const pins = this.providers.map((p, i) => {
const x = px(p.lng);
const y = py(p.lat);
const [lx, ly] = labelOffsets[p.name] || [10, 4];
return ` return `
<circle cx="${x}" cy="${y}" r="6" fill="${p.color}" stroke="#fff" stroke-width="1.5" opacity="0.9"> <g class="pin-group" data-idx="${i}">
<animate attributeName="r" values="6;8;6" dur="2s" repeatCount="indefinite" /> <!-- Pulse ring -->
<circle cx="${x}" cy="${y}" r="4" fill="none" stroke="${p.color}" stroke-width="1" opacity="0.5">
<animate attributeName="r" values="4;14;4" dur="3s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.5;0;0.5" dur="3s" repeatCount="indefinite" />
</circle> </circle>
<text x="${labelX}" y="${labelY}" fill="${p.color}" font-size="10" font-weight="600" font-family="system-ui, sans-serif">${this.esc(p.name)}</text> <!-- Drop pin body -->
<path d="M${x},${y - 2} c0,-7 -6,-12 -6,-16 a6,6 0 1,1 12,0 c0,4 -6,9 -6,16z" fill="${p.color}" stroke="#0f172a" stroke-width="0.8" opacity="0.92" />
<!-- Pin center dot -->
<circle cx="${x}" cy="${y - 14}" r="2.5" fill="#fff" opacity="0.85" />
<!-- Label -->
<text x="${x + lx}" y="${y + ly}" fill="${p.color}" font-size="10" font-weight="600" font-family="system-ui,sans-serif" opacity="0.9">${this.esc(p.name)}</text>
<text x="${x + lx}" y="${y + ly + 12}" fill="#64748b" font-size="8.5" font-family="system-ui,sans-serif">${this.esc(p.city)}</text>
</g>
`; `;
}).join(""); }).join("");
// Legend items
const legendItems = this.providers.map((p) => ` const legendItems = this.providers.map((p) => `
<div style="display:flex;align-items:center;gap:8px;padding:6px 0;"> <div style="display:flex;align-items:center;gap:8px;padding:5px 0;">
<div style="width:10px;height:10px;border-radius:50%;background:${p.color};flex-shrink:0;"></div> <div style="width:10px;height:10px;border-radius:50%;background:${p.color};flex-shrink:0;box-shadow:0 0 6px ${p.color}40;"></div>
<div> <div>
<span style="font-weight:600;font-size:13px;color:#e2e8f0;">${this.esc(p.name)}</span> <span style="font-weight:600;font-size:13px;color:#e2e8f0;">${this.esc(p.name)}</span>
<span style="font-size:12px;color:#64748b;margin-left:8px;">${this.esc(p.city)}</span> <span style="font-size:12px;color:#64748b;margin-left:8px;">${this.esc(p.city)}</span>
@ -84,61 +130,91 @@ class FolkMapViewer extends HTMLElement {
<style> <style>
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; } :host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
* { box-sizing: border-box; } * { box-sizing: border-box; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; } .demo-nav {
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; } display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; flex-wrap: wrap;
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.status-connected { background: #22c55e; }
.map-container {
width: 100%; border-radius: 10px;
background: #1a1a2e; border: 1px solid #333;
overflow: hidden; padding: 16px;
} }
.map-svg { width: 100%; height: auto; } .demo-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; min-width: 140px; }
.demo-nav__badge {
display: inline-flex; align-items: center; gap: 5px;
font-size: 11px; color: #94a3b8; background: rgba(16,185,129,0.1);
border: 1px solid rgba(16,185,129,0.2); border-radius: 20px; padding: 3px 10px;
}
.demo-nav__badge .dot { width: 6px; height: 6px; border-radius: 50%; background: #22c55e; }
.map-wrap {
width: 100%; border-radius: 12px; background: #0c1221; border: 1px solid #1e293b;
overflow: hidden; position: relative;
}
.map-svg { display: block; width: 100%; height: auto; }
/* Hover tooltip */
.pin-group { cursor: pointer; }
.tooltip {
position: absolute; pointer-events: none; opacity: 0; transition: opacity 0.15s;
background: #1e293b; border: 1px solid #334155; border-radius: 8px;
padding: 8px 12px; font-size: 12px; color: #e2e8f0; white-space: nowrap;
box-shadow: 0 4px 12px rgba(0,0,0,0.4); z-index: 10;
}
.tooltip.visible { opacity: 1; }
.tooltip strong { display: block; margin-bottom: 2px; }
.tooltip .city { color: #94a3b8; font-size: 11px; }
.tooltip .coords { color: #64748b; font-size: 10px; font-family: monospace; }
.legend { .legend {
background: rgba(15,23,42,0.5); border: 1px solid #1e293b; border-radius: 10px; background: rgba(15,23,42,0.6); border: 1px solid #1e293b; border-radius: 10px;
padding: 16px; margin-top: 16px; padding: 16px; margin-top: 16px;
} }
.legend-title { font-size: 13px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; } .legend-title {
font-size: 12px; font-weight: 600; color: #94a3b8;
text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px;
}
@media (max-width: 768px) { /* Feature highlights row */
.map-container { height: 300px; } .features {
display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 10px; margin-top: 16px;
}
.feat {
background: rgba(15,23,42,0.4); border: 1px solid #1e293b; border-radius: 10px;
padding: 12px; text-align: center;
}
.feat-icon { font-size: 20px; margin-bottom: 4px; }
.feat-label { font-size: 12px; font-weight: 600; color: #e2e8f0; }
.feat-desc { font-size: 10.5px; color: #64748b; margin-top: 2px; line-height: 1.4; }
@media (max-width: 640px) {
.features { grid-template-columns: repeat(2, 1fr); }
} }
</style> </style>
<div class="rapp-nav"> <div class="demo-nav">
<span class="rapp-nav__title">Cosmolocal Print Network</span> <span class="demo-nav__title">Cosmolocal Print Network</span>
<span class="status-dot status-connected"></span> <span class="demo-nav__badge"><span class="dot"></span> 6 providers online</span>
<span style="font-size:12px;color:#888;margin-left:4px;">6 providers</span>
</div> </div>
<div class="map-container"> <div class="map-wrap">
<svg class="map-svg" viewBox="0 0 ${mapWidth} ${mapHeight}" xmlns="http://www.w3.org/2000/svg"> <div class="tooltip" id="tooltip"></div>
<!-- Grid lines --> <svg class="map-svg" viewBox="0 0 ${W} ${H}" xmlns="http://www.w3.org/2000/svg">
<line x1="0" y1="${mapHeight / 2}" x2="${mapWidth}" y2="${mapHeight / 2}" stroke="#2a2a4e" stroke-width="0.5" stroke-dasharray="4,4" /> <defs>
<line x1="${mapWidth / 2}" y1="0" x2="${mapWidth / 2}" y2="${mapHeight}" stroke="#2a2a4e" stroke-width="0.5" stroke-dasharray="4,4" /> <radialGradient id="ocean" cx="50%" cy="40%" r="70%">
<line x1="0" y1="${mapHeight / 4}" x2="${mapWidth}" y2="${mapHeight / 4}" stroke="#1e1e3e" stroke-width="0.3" stroke-dasharray="2,4" /> <stop offset="0%" stop-color="#0f1b33" />
<line x1="0" y1="${mapHeight * 3 / 4}" x2="${mapWidth}" y2="${mapHeight * 3 / 4}" stroke="#1e1e3e" stroke-width="0.3" stroke-dasharray="2,4" /> <stop offset="100%" stop-color="#060d1a" />
<line x1="${mapWidth / 4}" y1="0" x2="${mapWidth / 4}" y2="${mapHeight}" stroke="#1e1e3e" stroke-width="0.3" stroke-dasharray="2,4" /> </radialGradient>
<line x1="${mapWidth * 3 / 4}" y1="0" x2="${mapWidth * 3 / 4}" y2="${mapHeight}" stroke="#1e1e3e" stroke-width="0.3" stroke-dasharray="2,4" /> </defs>
<!-- Simplified continent outlines --> <!-- Ocean background -->
<!-- North America --> <rect width="${W}" height="${H}" fill="url(#ocean)" />
<path d="M80,60 Q120,55 140,80 L160,90 Q170,110 150,130 L130,150 Q110,160 100,150 L80,120 Q60,90 80,60Z" fill="#2a2a4e" opacity="0.5" />
<!-- South America --> <!-- Graticule (lat/lng grid) -->
<path d="M160,180 Q170,170 175,190 L180,230 Q185,260 170,280 L160,290 Q145,280 150,250 L155,220 Q150,200 160,180Z" fill="#2a2a4e" opacity="0.5" /> ${this.graticule(W, H)}
<!-- Europe -->
<path d="M380,65 Q400,55 420,60 L440,70 Q445,85 430,95 L410,100 Q390,95 380,80Z" fill="#2a2a4e" opacity="0.5" /> <!-- Simplified continent fills -->
<!-- Africa --> ${this.continents(W, H)}
<path d="M390,110 Q410,105 430,115 L440,140 Q445,180 430,220 L415,240 Q400,245 390,230 L385,190 Q380,150 390,110Z" fill="#2a2a4e" opacity="0.5" />
<!-- Asia --> <!-- Connection arcs -->
<path d="M440,50 Q500,40 560,55 L600,70 Q640,80 660,100 L670,130 Q660,140 640,130 L600,110 Q550,100 500,90 L460,85 Q440,75 440,50Z" fill="#2a2a4e" opacity="0.5" /> ${arcs}
<!-- Australia -->
<path d="M600,220 Q630,210 650,220 L660,240 Q655,260 640,265 L620,260 Q600,250 600,220Z" fill="#2a2a4e" opacity="0.5" />
<!-- Japan -->
<path d="M665,85 Q670,80 672,90 L670,100 Q665,105 663,95Z" fill="#2a2a4e" opacity="0.5" />
<!-- Provider pins --> <!-- Provider pins -->
${providerDots} ${pins}
</svg> </svg>
</div> </div>
@ -146,7 +222,199 @@ class FolkMapViewer extends HTMLElement {
<div class="legend-title">Print Providers</div> <div class="legend-title">Print Providers</div>
${legendItems} ${legendItems}
</div> </div>
<div class="features">
<div class="feat">
<div class="feat-icon">&#128205;</div>
<div class="feat-label">Live GPS</div>
<div class="feat-desc">Real-time location sharing via WebSocket</div>
</div>
<div class="feat">
<div class="feat-icon">&#127970;</div>
<div class="feat-label">Indoor Nav</div>
<div class="feat-desc">c3nav integration for CCC events</div>
</div>
<div class="feat">
<div class="feat-icon">&#128204;</div>
<div class="feat-label">Waypoints</div>
<div class="feat-desc">Drop meeting points and pins</div>
</div>
<div class="feat">
<div class="feat-icon">&#128279;</div>
<div class="feat-label">QR Sharing</div>
<div class="feat-desc">Scan to join any room instantly</div>
</div>
<div class="feat">
<div class="feat-icon">&#128225;</div>
<div class="feat-label">Ping Friends</div>
<div class="feat-desc">Request location with one tap</div>
</div>
<div class="feat">
<div class="feat-icon">&#128737;</div>
<div class="feat-label">Privacy First</div>
<div class="feat-desc">Ghost mode, precision levels, zero tracking</div>
</div>
</div>
`; `;
this.attachDemoListeners();
}
/** Generate SVG graticule lines */
private graticule(W: number, H: number): string {
const lines: string[] = [];
// Latitude lines every 30 degrees
for (let lat = -60; lat <= 60; lat += 30) {
const y = ((90 - lat) / 180) * H;
lines.push(`<line x1="0" y1="${y}" x2="${W}" y2="${y}" stroke="#1a2744" stroke-width="0.5" stroke-dasharray="3,5" />`);
}
// Longitude lines every 30 degrees
for (let lng = -150; lng <= 180; lng += 30) {
const x = ((lng + 180) / 360) * W;
lines.push(`<line x1="${x}" y1="0" x2="${x}" y2="${H}" stroke="#1a2744" stroke-width="0.5" stroke-dasharray="3,5" />`);
}
// Equator and Prime Meridian slightly brighter
const eq = ((90 - 0) / 180) * H;
const pm = ((0 + 180) / 360) * W;
lines.push(`<line x1="0" y1="${eq}" x2="${W}" y2="${eq}" stroke="#1e3050" stroke-width="0.7" stroke-dasharray="4,3" />`);
lines.push(`<line x1="${pm}" y1="0" x2="${pm}" y2="${H}" stroke="#1e3050" stroke-width="0.7" stroke-dasharray="4,3" />`);
return lines.join("\n");
}
/** Simplified continent outlines using equirectangular projection */
private continents(W: number, H: number): string {
const p = (lat: number, lng: number) => {
const x = ((lng + 180) / 360) * W;
const y = ((90 - lat) / 180) * H;
return `${x.toFixed(1)},${y.toFixed(1)}`;
};
const fill = "#162236";
const stroke = "#1e3050";
// Each continent as a polygon path
const continents = [
// North America
`M${p(50, -130)} L${p(60, -130)} L${p(65, -120)} L${p(70, -100)} L${p(72, -80)}
L${p(65, -65)} L${p(50, -55)} L${p(45, -65)} L${p(40, -75)}
L${p(30, -82)} L${p(28, -90)} L${p(25, -100)} L${p(20, -105)}
L${p(18, -100)} L${p(15, -88)} L${p(10, -84)} L${p(10, -78)}
L${p(20, -88)} L${p(25, -80)} L${p(30, -82)} L${p(30, -75)}
L${p(40, -75)} L${p(48, -90)} L${p(48, -95)}
L${p(50, -120)} Z`,
// South America
`M${p(12, -75)} L${p(10, -68)} L${p(5, -60)} L${p(0, -50)}
L${p(-5, -45)} L${p(-10, -38)} L${p(-15, -40)} L${p(-20, -42)}
L${p(-25, -48)} L${p(-30, -50)} L${p(-35, -56)} L${p(-40, -62)}
L${p(-45, -66)} L${p(-50, -70)} L${p(-55, -68)}
L${p(-50, -74)} L${p(-42, -72)} L${p(-35, -70)}
L${p(-25, -70)} L${p(-20, -70)} L${p(-15, -76)}
L${p(-5, -78)} L${p(0, -80)} L${p(5, -78)} L${p(10, -76)} Z`,
// Europe
`M${p(40, -8)} L${p(42, 0)} L${p(44, 5)} L${p(46, 8)}
L${p(48, 10)} L${p(50, 5)} L${p(52, 8)} L${p(55, 10)}
L${p(58, 12)} L${p(60, 10)} L${p(62, 18)} L${p(65, 20)}
L${p(70, 25)} L${p(68, 30)} L${p(62, 32)} L${p(58, 30)}
L${p(55, 28)} L${p(50, 30)} L${p(48, 28)} L${p(45, 25)}
L${p(42, 28)} L${p(38, 25)} L${p(36, 22)} L${p(38, 10)}
L${p(38, 0)} Z`,
// Africa
`M${p(35, -5)} L${p(37, 10)} L${p(35, 20)} L${p(32, 32)}
L${p(28, 33)} L${p(15, 42)} L${p(10, 42)} L${p(5, 38)}
L${p(0, 40)} L${p(-5, 38)} L${p(-10, 34)} L${p(-15, 30)}
L${p(-20, 28)} L${p(-25, 28)} L${p(-30, 28)} L${p(-34, 25)}
L${p(-34, 20)} L${p(-30, 15)} L${p(-20, 12)} L${p(-15, 12)}
L${p(-10, 15)} L${p(-5, 10)} L${p(0, 8)} L${p(5, 2)}
L${p(10, 0)} L${p(15, -5)} L${p(20, -10)} L${p(25, -15)}
L${p(30, -10)} L${p(35, -5)} Z`,
// Asia (mainland)
`M${p(70, 30)} L${p(72, 50)} L${p(72, 80)} L${p(70, 110)}
L${p(68, 140)} L${p(65, 165)} L${p(60, 165)} L${p(55, 140)}
L${p(50, 130)} L${p(45, 135)} L${p(40, 130)} L${p(35, 120)}
L${p(30, 120)} L${p(25, 105)} L${p(22, 100)} L${p(20, 98)}
L${p(15, 100)} L${p(10, 105)} L${p(5, 100)} L${p(0, 104)}
L${p(-5, 105)} L${p(-8, 112)} L${p(-6, 118)}
L${p(2, 110)} L${p(8, 108)} L${p(12, 110)}
L${p(20, 108)} L${p(22, 114)} L${p(30, 110)}
L${p(28, 88)} L${p(25, 68)} L${p(30, 50)}
L${p(35, 45)} L${p(40, 40)} L${p(42, 32)}
L${p(48, 30)} L${p(55, 30)} L${p(62, 32)}
L${p(68, 30)} Z`,
// Australia
`M${p(-12, 132)} L${p(-15, 140)} L${p(-20, 148)} L${p(-25, 150)}
L${p(-30, 148)} L${p(-35, 140)} L${p(-38, 146)} L${p(-35, 150)}
L${p(-32, 152)} L${p(-28, 153)} L${p(-25, 152)}
L${p(-20, 150)} L${p(-15, 145)} L${p(-12, 138)}
L${p(-14, 130)} L${p(-18, 122)} L${p(-22, 115)}
L${p(-28, 114)} L${p(-32, 116)} L${p(-35, 118)}
L${p(-34, 125)} L${p(-30, 130)} L${p(-25, 132)}
L${p(-20, 130)} L${p(-16, 128)} L${p(-12, 132)} Z`,
// Japan (simplified)
`M${p(35, 133)} L${p(38, 136)} L${p(40, 140)} L${p(42, 142)}
L${p(44, 144)} L${p(45, 142)} L${p(43, 140)} L${p(40, 137)}
L${p(37, 135)} L${p(35, 133)} Z`,
// UK/Ireland
`M${p(51, -5)} L${p(53, 0)} L${p(55, -2)} L${p(57, -5)}
L${p(58, -3)} L${p(56, 0)} L${p(54, 1)} L${p(52, 0)}
L${p(50, -3)} L${p(51, -5)} Z`,
// Greenland
`M${p(62, -50)} L${p(68, -52)} L${p(75, -45)} L${p(78, -35)}
L${p(76, -20)} L${p(70, -22)} L${p(65, -35)} L${p(62, -45)} Z`,
// Indonesia (simplified)
`M${p(-2, 100)} L${p(-4, 108)} L${p(-6, 112)} L${p(-8, 115)}
L${p(-7, 118)} L${p(-5, 116)} L${p(-3, 112)} L${p(-1, 106)} L${p(-2, 100)} Z`,
// New Zealand
`M${p(-36, 174)} L${p(-38, 176)} L${p(-42, 174)} L${p(-46, 168)}
L${p(-44, 168)} L${p(-42, 172)} L${p(-38, 174)} L${p(-36, 174)} Z`,
];
return continents.map((d) =>
`<path d="${d.replace(/\s+/g, " ").trim()}" fill="${fill}" stroke="${stroke}" stroke-width="0.5" opacity="0.75" />`
).join("\n");
}
private attachDemoListeners() {
const tooltip = this.shadow.getElementById("tooltip");
if (!tooltip) return;
this.shadow.querySelectorAll(".pin-group").forEach((el) => {
const idx = parseInt((el as HTMLElement).dataset.idx || "0", 10);
const p = this.providers[idx];
el.addEventListener("mouseenter", (e) => {
const rect = this.shadow.querySelector(".map-wrap")?.getBoundingClientRect();
const me = e as MouseEvent;
if (rect) {
tooltip.innerHTML = `<strong>${this.esc(p.name)}</strong><span class="city">${this.esc(p.city)}</span><span class="coords">${p.lat.toFixed(2)}, ${p.lng.toFixed(2)}</span>`;
tooltip.style.left = `${me.clientX - rect.left + 12}px`;
tooltip.style.top = `${me.clientY - rect.top - 10}px`;
tooltip.classList.add("visible");
}
});
el.addEventListener("mousemove", (e) => {
const rect = this.shadow.querySelector(".map-wrap")?.getBoundingClientRect();
const me = e as MouseEvent;
if (rect) {
tooltip.style.left = `${me.clientX - rect.left + 12}px`;
tooltip.style.top = `${me.clientY - rect.top - 10}px`;
}
});
el.addEventListener("mouseleave", () => {
tooltip.classList.remove("visible");
});
});
} }
private getApiBase(): string { private getApiBase(): string {
@ -288,7 +556,7 @@ class FolkMapViewer extends HTMLElement {
${this.rooms.length > 0 ? this.rooms.map((r) => ` ${this.rooms.length > 0 ? this.rooms.map((r) => `
<div class="room-card" data-room="${r}"> <div class="room-card" data-room="${r}">
<span class="room-icon">🗺</span> <span class="room-icon">&#128506;</span>
<span class="room-name">${this.esc(r)}</span> <span class="room-name">${this.esc(r)}</span>
</div> </div>
`).join("") : ""} `).join("") : ""}
@ -304,14 +572,14 @@ class FolkMapViewer extends HTMLElement {
const shareUrl = `${window.location.origin}/${this.space}/maps/${this.room}`; const shareUrl = `${window.location.origin}/${this.space}/maps/${this.room}`;
return ` return `
<div class="rapp-nav"> <div class="rapp-nav">
<button class="rapp-nav__back" data-back="lobby"> Rooms</button> <button class="rapp-nav__back" data-back="lobby">&#8592; Rooms</button>
<span class="rapp-nav__title">🗺 ${this.esc(this.room)}</span> <span class="rapp-nav__title">&#128506; ${this.esc(this.room)}</span>
<span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}"></span> <span class="status-dot ${this.syncStatus === "connected" ? "status-connected" : "status-disconnected"}"></span>
</div> </div>
<div class="map-container"> <div class="map-container">
<div class="map-placeholder"> <div class="map-placeholder">
<p style="font-size:48px">🌍</p> <p style="font-size:48px">&#127758;</p>
<p style="font-size:16px">Map Room: <strong>${this.esc(this.room)}</strong></p> <p style="font-size:16px">Map Room: <strong>${this.esc(this.room)}</strong></p>
<p>Connect the MapLibre GL library to display the interactive map.</p> <p>Connect the MapLibre GL library to display the interactive map.</p>
<p style="font-size:12px;color:#555">WebSocket sync: ${this.syncStatus}</p> <p style="font-size:12px;color:#555">WebSocket sync: ${this.syncStatus}</p>

View File

@ -10,6 +10,16 @@ interface GraphNode {
name: string; name: string;
type: "person" | "company" | "opportunity"; type: "person" | "company" | "opportunity";
workspace: string; workspace: string;
role?: string;
location?: string;
description?: string;
}
interface GraphEdge {
source: string;
target: string;
type: "work_at" | "point_of_contact" | "collaborates";
label?: string;
} }
class FolkGraphViewer extends HTMLElement { class FolkGraphViewer extends HTMLElement {
@ -18,6 +28,7 @@ class FolkGraphViewer extends HTMLElement {
private workspaces: any[] = []; private workspaces: any[] = [];
private info: any = null; private info: any = null;
private nodes: GraphNode[] = []; private nodes: GraphNode[] = [];
private edges: GraphEdge[] = [];
private filter: "all" | "person" | "company" | "opportunity" = "all"; private filter: "all" | "person" | "company" | "opportunity" = "all";
private searchQuery = ""; private searchQuery = "";
private error = ""; private error = "";
@ -35,21 +46,59 @@ class FolkGraphViewer extends HTMLElement {
} }
private loadDemoData() { private loadDemoData() {
this.info = { name: "rSpace Community", member_count: 42, company_count: 8, opportunity_count: 5 }; this.info = { name: "rSpace Community", member_count: 10, company_count: 3, opportunity_count: 0 };
this.workspaces = [ this.workspaces = [
{ name: "Core Contributors", slug: "core-contributors", nodeCount: 12, edgeCount: 3 }, { name: "Commons DAO", slug: "commons-dao", nodeCount: 5, edgeCount: 4 },
{ name: "Extended Network", slug: "extended-network", nodeCount: 30, edgeCount: 5 }, { name: "Mycelial Lab", slug: "mycelial-lab", nodeCount: 5, edgeCount: 4 },
{ name: "Regenerative Fund", slug: "regenerative-fund", nodeCount: 5, edgeCount: 4 },
]; ];
// Organizations
this.nodes = [ this.nodes = [
{ id: "demo-p1", name: "Alice Chen", type: "person", workspace: "Core Contributors" }, { id: "org-1", name: "Commons DAO", type: "company", workspace: "Commons DAO", description: "Decentralized governance cooperative" },
{ id: "demo-p2", name: "Bob Marley", type: "person", workspace: "Core Contributors" }, { id: "org-2", name: "Mycelial Lab", type: "company", workspace: "Mycelial Lab", description: "Protocol research collective" },
{ id: "demo-p3", name: "Carol Danvers", type: "person", workspace: "Extended Network" }, { id: "org-3", name: "Regenerative Fund", type: "company", workspace: "Regenerative Fund", description: "Impact funding vehicle" },
{ id: "demo-p4", name: "Diana Prince", type: "person", workspace: "Extended Network" },
{ id: "demo-c1", name: "Radiant Hall Press", type: "company", workspace: "Core Contributors" }, // People — Commons DAO
{ id: "demo-c2", name: "Tiny Splendor", type: "company", workspace: "Extended Network" }, { id: "p-1", name: "Alice Chen", type: "person", workspace: "Commons DAO", role: "Lead Engineer", location: "Vancouver" },
{ id: "demo-c3", name: "Commons Hub", type: "company", workspace: "Core Contributors" }, { id: "p-2", name: "Bob Nakamura", type: "person", workspace: "Commons DAO", role: "Community Lead", location: "Tokyo" },
{ id: "p-3", name: "Carol Santos", type: "person", workspace: "Commons DAO", role: "Treasury Steward", location: "S\u00e3o Paulo" },
{ id: "p-4", name: "Dave Okafor", type: "person", workspace: "Commons DAO", role: "Governance Facilitator", location: "Lagos" },
// People — Mycelial Lab
{ id: "p-5", name: "Eva Larsson", type: "person", workspace: "Mycelial Lab", role: "Ops Coordinator", location: "Stockholm" },
{ id: "p-6", name: "Frank M\u00fcller", type: "person", workspace: "Mycelial Lab", role: "Protocol Designer", location: "Berlin" },
{ id: "p-7", name: "Grace Kim", type: "person", workspace: "Mycelial Lab", role: "Strategy Lead", location: "Seoul" },
// People — Regenerative Fund
{ id: "p-8", name: "Hiro Tanaka", type: "person", workspace: "Regenerative Fund", role: "Research Lead", location: "Osaka" },
{ id: "p-9", name: "Iris Patel", type: "person", workspace: "Regenerative Fund", role: "Developer Relations", location: "Mumbai" },
{ id: "p-10", name: "James Wright", type: "person", workspace: "Regenerative Fund", role: "Security Auditor", location: "London" },
];
// Edges: work_at links + cross-org point_of_contact
this.edges = [
// Work_at — Commons DAO
{ source: "p-1", target: "org-1", type: "work_at" },
{ source: "p-2", target: "org-1", type: "work_at" },
{ source: "p-3", target: "org-1", type: "work_at" },
{ source: "p-4", target: "org-1", type: "work_at" },
// Work_at — Mycelial Lab
{ source: "p-5", target: "org-2", type: "work_at" },
{ source: "p-6", target: "org-2", type: "work_at" },
{ source: "p-7", target: "org-2", type: "work_at" },
// Work_at — Regenerative Fund
{ source: "p-8", target: "org-3", type: "work_at" },
{ source: "p-9", target: "org-3", type: "work_at" },
{ source: "p-10", target: "org-3", type: "work_at" },
// Cross-org point_of_contact edges
{ source: "p-1", target: "p-6", type: "point_of_contact", label: "Alice \u2194 Frank" },
{ source: "p-2", target: "p-3", type: "point_of_contact", label: "Bob \u2194 Carol" },
{ source: "p-4", target: "p-7", type: "point_of_contact", label: "Dave \u2194 Grace" },
]; ];
this.render(); this.render();
@ -83,7 +132,10 @@ class FolkGraphViewer extends HTMLElement {
const q = this.searchQuery.toLowerCase(); const q = this.searchQuery.toLowerCase();
filtered = filtered.filter(n => filtered = filtered.filter(n =>
n.name.toLowerCase().includes(q) || n.name.toLowerCase().includes(q) ||
n.workspace.toLowerCase().includes(q) n.workspace.toLowerCase().includes(q) ||
(n.role && n.role.toLowerCase().includes(q)) ||
(n.location && n.location.toLowerCase().includes(q)) ||
(n.description && n.description.toLowerCase().includes(q))
); );
} }
return filtered; return filtered;
@ -105,39 +157,121 @@ class FolkGraphViewer extends HTMLElement {
`; `;
} }
// Render demo nodes as positioned circles inside the graph canvas const W = 700;
const cx = 250; const H = 500;
const cy = 250; const filteredIds = new Set(filtered.map(n => n.id));
const r = 180;
const nodesSvg = filtered.map((node, i) => { // Cluster layout: position org nodes as hubs, people orbit around their org
const angle = (2 * Math.PI * i) / filtered.length - Math.PI / 2; // Three orgs arranged in a triangle
const x = cx + r * Math.cos(angle); const orgCenters: Record<string, { x: number; y: number }> = {
const y = cy + r * Math.sin(angle); "org-1": { x: W / 2, y: 120 }, // Commons DAO — top center
const color = node.type === "person" ? "#3b82f6" : node.type === "company" ? "#22c55e" : "#f59e0b"; "org-2": { x: 160, y: 380 }, // Mycelial Lab — bottom left
const radius = node.type === "company" ? 18 : 14; "org-3": { x: W - 160, y: 380 }, // Regenerative Fund — bottom right
};
// Build a position map for all nodes
const positions: Record<string, { x: number; y: number }> = {};
// Position org nodes at their centers
for (const [id, pos] of Object.entries(orgCenters)) {
positions[id] = pos;
}
// Group people by their workspace (org)
const orgNameToId: Record<string, string> = {
"Commons DAO": "org-1",
"Mycelial Lab": "org-2",
"Regenerative Fund": "org-3",
};
const peopleByOrg: Record<string, GraphNode[]> = {};
for (const node of this.nodes) {
if (node.type === "person") {
const orgId = orgNameToId[node.workspace];
if (orgId) {
if (!peopleByOrg[orgId]) peopleByOrg[orgId] = [];
peopleByOrg[orgId].push(node);
}
}
}
// Position people in a semicircle around their org
const orbitRadius = 110;
for (const [orgId, people] of Object.entries(peopleByOrg)) {
const center = orgCenters[orgId];
if (!center) continue;
const count = people.length;
// Spread people in an arc facing outward from the graph center
const graphCx = W / 2;
const graphCy = (120 + 380) / 2; // vertical center of the triangle
const baseAngle = Math.atan2(center.y - graphCy, center.x - graphCx);
const spread = Math.PI * 0.8; // 144 degrees arc
for (let i = 0; i < count; i++) {
const angle = baseAngle - spread / 2 + (spread * i) / Math.max(count - 1, 1);
positions[people[i].id] = {
x: center.x + orbitRadius * Math.cos(angle),
y: center.y + orbitRadius * Math.sin(angle),
};
}
}
// Org background cluster circles
const orgColors: Record<string, string> = {
"org-1": "#6366f1", // indigo for Commons DAO
"org-2": "#22c55e", // green for Mycelial Lab
"org-3": "#f59e0b", // amber for Regenerative Fund
};
const clustersSvg = Object.entries(orgCenters).map(([orgId, pos]) => {
const color = orgColors[orgId] || "#333";
return `<circle cx="${pos.x}" cy="${pos.y}" r="${orbitRadius + 30}" fill="${color}" opacity="0.04" stroke="${color}" stroke-width="1" stroke-opacity="0.15" stroke-dasharray="4 4"/>`;
}).join("");
// Render edges
const edgesSvg: string[] = [];
for (const edge of this.edges) {
const sp = positions[edge.source];
const tp = positions[edge.target];
if (!sp || !tp) continue;
if (!filteredIds.has(edge.source) || !filteredIds.has(edge.target)) continue;
if (edge.type === "work_at") {
edgesSvg.push(`<line x1="${sp.x}" y1="${sp.y}" x2="${tp.x}" y2="${tp.y}" stroke="#555" stroke-width="1" opacity="0.35"/>`);
} else if (edge.type === "point_of_contact") {
// Cross-org edges: dashed, brighter
const mx = (sp.x + tp.x) / 2;
const my = (sp.y + tp.y) / 2;
edgesSvg.push(`<line x1="${sp.x}" y1="${sp.y}" x2="${tp.x}" y2="${tp.y}" stroke="#c084fc" stroke-width="1.5" stroke-dasharray="6 3" opacity="0.6"/>`);
if (edge.label) {
edgesSvg.push(`<text x="${mx}" y="${my - 6}" fill="#c084fc" font-size="8" text-anchor="middle" opacity="0.7">${this.esc(edge.label)}</text>`);
}
}
}
// Render nodes
const nodesSvg = filtered.map(node => {
const pos = positions[node.id];
if (!pos) return "";
const isOrg = node.type === "company";
const color = isOrg ? (orgColors[node.id] || "#22c55e") : "#3b82f6";
const radius = isOrg ? 22 : 12;
let label = this.esc(node.name);
let sublabel = "";
if (isOrg && node.description) {
sublabel = `<text x="${pos.x}" y="${pos.y + radius + 26}" fill="#888" font-size="8" text-anchor="middle">${this.esc(node.description)}</text>`;
} else if (!isOrg && node.role) {
sublabel = `<text x="${pos.x}" y="${pos.y + radius + 24}" fill="#666" font-size="8" text-anchor="middle">${this.esc(node.role)}${node.location ? " \u00b7 " + this.esc(node.location) : ""}</text>`;
}
return ` return `
<circle cx="${x}" cy="${y}" r="${radius}" fill="${color}" opacity="0.8"/> <circle cx="${pos.x}" cy="${pos.y}" r="${radius}" fill="${color}" opacity="${isOrg ? 0.9 : 0.75}" stroke="${isOrg ? color : "none"}" stroke-width="${isOrg ? 2 : 0}" stroke-opacity="0.3"/>
<text x="${x}" y="${y + radius + 14}" fill="#aaa" font-size="10" text-anchor="middle">${this.esc(node.name)}</text> ${isOrg ? `<text x="${pos.x}" y="${pos.y + 4}" fill="#fff" font-size="9" font-weight="600" text-anchor="middle">${label.length > 14 ? label.slice(0, 12) + "\u2026" : label}</text>` : ""}
<text x="${pos.x}" y="${pos.y + radius + 13}" fill="#ccc" font-size="${isOrg ? 11 : 10}" font-weight="${isOrg ? 600 : 400}" text-anchor="middle">${label}</text>
${sublabel}
`; `;
}).join(""); }).join("");
// Draw edges between nodes in the same workspace return `<svg viewBox="0 0 ${W} ${H}" width="100%" height="100%" style="max-height:500px">${clustersSvg}${edgesSvg.join("")}${nodesSvg}</svg>`;
const edgesSvg: string[] = [];
for (let i = 0; i < filtered.length; i++) {
for (let j = i + 1; j < filtered.length; j++) {
if (filtered[i].workspace === filtered[j].workspace) {
const a1 = (2 * Math.PI * i) / filtered.length - Math.PI / 2;
const a2 = (2 * Math.PI * j) / filtered.length - Math.PI / 2;
const x1 = cx + r * Math.cos(a1);
const y1 = cy + r * Math.sin(a1);
const x2 = cx + r * Math.cos(a2);
const y2 = cy + r * Math.sin(a2);
edgesSvg.push(`<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#333" stroke-width="1" opacity="0.4"/>`);
}
}
}
return `<svg viewBox="0 0 500 500" width="100%" height="100%" style="max-height:500px">${edgesSvg.join("")}${nodesSvg}</svg>`;
} }
private render() { private render() {
@ -183,6 +317,7 @@ class FolkGraphViewer extends HTMLElement {
.legend { display: flex; gap: 16px; margin-top: 12px; } .legend { display: flex; gap: 16px; margin-top: 12px; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #888; } .legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #888; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; } .legend-dot { width: 10px; height: 10px; border-radius: 50%; }
.legend-line { display: inline-block; }
.dot-person { background: #3b82f6; } .dot-person { background: #3b82f6; }
.dot-company { background: #22c55e; } .dot-company { background: #22c55e; }
.dot-opportunity { background: #f59e0b; } .dot-opportunity { background: #f59e0b; }
@ -211,17 +346,18 @@ class FolkGraphViewer extends HTMLElement {
${this.info ? ` ${this.info ? `
<div class="stats"> <div class="stats">
<div class="stat"><div class="stat-value">${this.info.member_count || 0}</div><div class="stat-label">Members</div></div> <div class="stat"><div class="stat-value">${this.info.member_count || 0}</div><div class="stat-label">People</div></div>
<div class="stat"><div class="stat-value">${this.info.company_count || 0}</div><div class="stat-label">Companies</div></div> <div class="stat"><div class="stat-value">${this.info.company_count || 0}</div><div class="stat-label">Organizations</div></div>
<div class="stat"><div class="stat-value">${this.info.opportunity_count || 0}</div><div class="stat-label">Opportunities</div></div> <div class="stat"><div class="stat-value">${this.edges?.filter((e: GraphEdge) => e.type === "point_of_contact").length || 0}</div><div class="stat-label">Cross-org Links</div></div>
</div> </div>
` : ""} ` : ""}
<div class="toolbar"> <div class="toolbar">
<input class="search-input" type="text" placeholder="Search nodes..." id="search-input" value="${this.esc(this.searchQuery)}"> <input class="search-input" type="text" placeholder="Search nodes..." id="search-input" value="${this.esc(this.searchQuery)}">
${(["all", "person", "company", "opportunity"] as const).map(f => ${(["all", "person", "company", "opportunity"] as const).map(f => {
`<button class="filter-btn ${this.filter === f ? "active" : ""}" data-filter="${f}">${f === "all" ? "All" : f.charAt(0).toUpperCase() + f.slice(1)}s</button>` const labels: Record<string, string> = { all: "All", person: "People", company: "Organizations", opportunity: "Opportunities" };
).join("")} return `<button class="filter-btn ${this.filter === f ? "active" : ""}" data-filter="${f}">${labels[f]}</button>`;
}).join("")}
</div> </div>
<div class="graph-canvas"> <div class="graph-canvas">
@ -237,12 +373,13 @@ class FolkGraphViewer extends HTMLElement {
<div class="legend"> <div class="legend">
<div class="legend-item"><span class="legend-dot dot-person"></span> People</div> <div class="legend-item"><span class="legend-dot dot-person"></span> People</div>
<div class="legend-item"><span class="legend-dot dot-company"></span> Companies</div> <div class="legend-item"><span class="legend-dot dot-company"></span> Organizations</div>
<div class="legend-item"><span class="legend-dot dot-opportunity"></span> Opportunities</div> <div class="legend-item"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#555" stroke-width="2"/></svg> Works at</div>
<div class="legend-item"><svg width="20" height="10"><line x1="0" y1="5" x2="20" y2="5" stroke="#c084fc" stroke-width="2" stroke-dasharray="4 2"/></svg> Point of contact</div>
</div> </div>
${this.workspaces.length > 0 ? ` ${this.workspaces.length > 0 ? `
<div style="margin-top:20px;font-size:14px;font-weight:600;color:#aaa">Workspaces</div> <div style="margin-top:20px;font-size:14px;font-weight:600;color:#aaa">${this.space === "demo" ? "Organizations" : "Workspaces"}</div>
<div class="workspace-list"> <div class="workspace-list">
${this.workspaces.map(ws => ` ${this.workspaces.map(ws => `
<div class="ws-card"> <div class="ws-card">

View File

@ -86,121 +86,479 @@ class FolkNotesApp extends HTMLElement {
const hour = 3600000; const hour = 3600000;
const day = 86400000; const day = 86400000;
const projectNotes: Note[] = [ const tripPlanningNotes: Note[] = [
{ {
id: "demo-note-1", title: "Cosmolocal Marketplace", id: "demo-note-1", title: "Pre-trip Preparation",
content: "Build a decentralized marketplace connecting local makers with global designers. Use rCart for orders, rFunds for revenue splits.", content: `## Pre-trip Preparation
content_plain: "Build a decentralized marketplace connecting local makers with global designers. Use rCart for orders, rFunds for revenue splits.",
type: "NOTE", tags: ["cosmolocal", "marketplace"], is_pinned: true, ### Flights & Transfers
created_at: new Date(now - 2 * day).toISOString(), updated_at: new Date(now - hour).toISOString(), - **Jul 6**: Fly Geneva, shuttle to Chamonix (~1.5h)
- **Jul 14**: Train Zermatt to Dolomites (Bernina Express, ~6h scenic route)
- **Jul 20**: Fly home from Innsbruck
> Book the Aiguille du Midi cable car tickets at least 2 weeks in advance -- they sell out fast in July.
### Travel Documents
1. Passports (valid 6+ months)
2. EU health insurance cards (EHIC)
3. Travel insurance policy (ref: WA-2026-7891)
4. Hut reservation confirmations (printed copies)
5. Drone registration for Italy
### Budget Overview
Total budget: **EUR 4,000** across 4 travelers.
\`\`\`
Transport: EUR 800 (20%)
Accommodation: EUR 1200 (30%)
Activities: EUR 1000 (25%)
Food: EUR 600 (15%)
Gear: EUR 400 (10%)
\`\`\`
*Maya is tracking expenses in rFunds. Current spend: EUR 1,203.*`,
content_plain: "Pre-trip preparation checklist covering flights, transfers, travel documents, and budget overview for the Alpine Explorer 2026 trip.",
type: "NOTE", tags: ["planning", "budget", "transport"], is_pinned: true,
created_at: new Date(now - 14 * day).toISOString(), updated_at: new Date(now - hour).toISOString(),
}, },
{ {
id: "demo-note-2", title: "Community Garden App", id: "demo-note-2", title: "Accommodation Research",
content: "Track plots, plantings, and harvests. Share surplus through a local exchange network.", content: `## Accommodation Research
content_plain: "Track plots, plantings, and harvests. Share surplus through a local exchange network.",
type: "NOTE", tags: ["community", "local"], is_pinned: false, ### Chamonix (Jul 6-10)
created_at: new Date(now - 3 * day).toISOString(), updated_at: new Date(now - 2 * hour).toISOString(), - **Refuge du Lac Blanc** -- Jul 7, 4 beds, conf #LB2026-234
- Airbnb in town for other nights (~EUR 120/night for 4 pax)
- Consider Hotel Le Morgane if Airbnb falls through
### Zermatt (Jul 10-14)
- **Hornlihutte** (Matterhorn base) -- Waitlisted for Jul 12
- Main accommodation: Apartment near Bahnhofstrasse
- Car-free village, arrive by Glacier Express
> Zermatt is expensive. Budget EUR 80-100pp/night minimum. The apartment saves us about 40% vs hotels.
### Dolomites (Jul 14-20)
- **Rifugio Locatelli** -- Jul 15, 4 beds, conf #TRE2026-089
- Val Gardena base: Ortisei area
- Look for agriturismo options for authentic experience
### Booking Status
| Location | Status | Cost/Night | Notes |
|----------|--------|-----------|-------|
| Lac Blanc | Confirmed | EUR 65pp | Half-board included |
| Chamonix Airbnb | Confirmed | EUR 120 total | 2-bed apartment |
| Zermatt apartment | Confirmed | EUR 180 total | Near station |
| Hornlihutte | Waitlisted | EUR 85pp | Fingers crossed |
| Locatelli | Confirmed | EUR 55pp | Half-board |
| Val Gardena | Searching | ~EUR 100 total | Need 4+ beds |`,
content_plain: "Accommodation research for all three destinations: Chamonix, Zermatt, and Dolomites. Includes confirmed bookings, waitlists, and budget estimates.",
type: "NOTE", tags: ["accommodation", "budget"], is_pinned: false,
created_at: new Date(now - 12 * day).toISOString(), updated_at: new Date(now - 3 * hour).toISOString(),
}, },
{ {
id: "demo-note-3", title: "Mesh Network Map", id: "demo-note-3", title: "Activity Planning",
content: "Visualize community mesh networks using rMaps. Show signal strength, coverage areas.", content: `## Activity Planning
content_plain: "Visualize community mesh networks using rMaps. Show signal strength, coverage areas.",
type: "NOTE", tags: ["mesh", "infrastructure"], is_pinned: false, ### Hiking Routes
created_at: new Date(now - 5 * day).toISOString(), updated_at: new Date(now - 3 * hour).toISOString(), - **Lac Blanc** (Jul 7) -- Acclimatization hike, ~6h round trip, 1000m elevation gain. Stunning Mont Blanc reflection at sunrise.
- **Gornergrat Sunrise** (Jul 11) -- Take the first train up at 7am, hike down. Matterhorn panorama.
- **Matterhorn Base Camp** (Jul 12) -- Full day trek to Hornlihutte. 1500m gain. Only if weather permits.
- **Tre Cime di Lavaredo** (Jul 15) -- Classic loop, ~4h. Stay at Rifugio Locatelli for golden hour photos.
- **Seceda Ridgeline** (Jul 17) -- Gondola up, ridge walk, hike down to Ortisei. Best drone location.
### Adventure Activities
1. **Via Ferrata at Aiguille du Midi** (Jul 8) -- Rent harness + lanyard + helmet in Chamonix, ~EUR 25/day
2. **Paragliding over Zermatt** (Jul 13) -- Tandem flights ~EUR 180pp. Book with Paragliding Zermatt (best reviews)
3. **Kayaking at Lago di Braies** (Jul 16) -- Turquoise glacial lake, kayak rental ~EUR 15/hour
### Rest Days
- Jul 9: Explore Chamonix town, gear shopping
- Jul 19: Free day before flying home, packing
> Omar suggested we vote on the Day 5 alternative activity -- Via Ferrata is winning 7-3 over kayaking on Lac d'Annecy.
### Difficulty Ratings
\`\`\`
Lac Blanc: Moderate (T2)
Gornergrat: Easy (T1)
Matterhorn Base: Difficult (T3)
Tre Cime Loop: Moderate (T2)
Seceda Ridge: Easy (T1)
Via Ferrata: Difficult (K3)
\`\`\``,
content_plain: "Detailed activity planning including hiking routes with difficulty ratings, adventure activities with costs, and rest day plans.",
type: "NOTE", tags: ["hiking", "activities", "adventure"], is_pinned: false,
created_at: new Date(now - 10 * day).toISOString(), updated_at: new Date(now - 5 * hour).toISOString(),
}, },
{ {
id: "demo-note-4", title: "Open Hardware Library", id: "demo-note-4", title: "Gear Research",
content: "Catalog open-source hardware designs. Link to local fabrication providers.", content: `## Gear Research
content_plain: "Catalog open-source hardware designs. Link to local fabrication providers.",
type: "NOTE", tags: ["hardware", "open-source"], is_pinned: false, ### Via Ferrata Kit
created_at: new Date(now - 7 * day).toISOString(), updated_at: new Date(now - 4 * hour).toISOString(), Need harness + lanyard + helmet. Can rent in Chamonix for ~EUR 25/day per person. Renting is better than buying for a one-time activity.
### Paragliding
Tandem flights in Zermatt: ~EUR 180pp. Book at **Paragliding Zermatt** (best reviews on TripAdvisor). They offer morning flights with better thermals.
### Camera & Drone
- Bring the **DJI Mini 4 Pro** for Tre Cime and Seceda
- Check Italian drone regulations! Need ENAC registration for flights over 250g
- ND filters for long exposure water shots at Lago di Braies
- Extra batteries (3x) -- cold altitude drains them fast
> Liam scored the DJI Mini 4 Pro over GoPro Hero 12 and Sony A7C II in our decision matrix. Best weight-to-quality ratio for hiking.
### Group Gear (Shared)
\`\`\`
Item Cost Status Owner
-------------------------- ------- ---------- -----
Adventure First-Aid Kit EUR 85 Funded Omar
Water Filter (Sawyer) EUR 45 Funded Maya
Bear Canisters 2x (BV500) EUR 120 In Cart Liam
Camp Stove + Fuel EUR 65 Funded Priya
DJI Mini 4 Pro Rental EUR 350 Needs Fund Liam
Starlink Mini Rental EUR 200 Needs Fund Omar
\`\`\`
### Personal Gear Checklist
- Hiking boots (broken in!)
- Rain jacket (waterproof, not just resistant)
- Headlamp + spare batteries
- Trekking poles (collapsible for flights)
- Sunscreen SPF 50 + lip balm
- Wool base layers for hut nights`,
content_plain: "Gear research including Via Ferrata rental, paragliding booking, camera and drone regulations, shared group gear status, and personal gear checklist.",
type: "NOTE", tags: ["gear", "equipment", "budget"], is_pinned: false,
created_at: new Date(now - 8 * day).toISOString(), updated_at: new Date(now - 2 * hour).toISOString(),
},
{
id: "demo-note-5", title: "Emergency Contacts & Safety",
content: `## Emergency Contacts & Safety
### Emergency Numbers
- **France**: 112 (EU general), PGHM Mountain Rescue: +33 4 50 53 16 89
- **Switzerland**: 1414 (REGA air rescue), 144 (ambulance)
- **Italy**: 118 (medical), 112 (general emergency)
### Insurance
- Policy #: **WA-2026-7891**
- Emergency line: +1-800-555-0199
- Covers: mountain rescue, helicopter evacuation, medical repatriation
- Deductible: EUR 150 per incident
### Altitude Sickness Protocol
1. Acclimatize in Chamonix (1,035m) for 2 days before going high
2. Stay hydrated -- minimum 3L water per day above 2,500m
3. Watch for symptoms: headache, nausea, dizziness
4. Descend immediately if symptoms worsen
5. Omar packed altitude sickness medication (Diamox) in the first-aid kit
### Weather Contingency
> If the Matterhorn trek gets rained out, Omar found a cheese museum in Zermatt as backup. Priya suggests the Glacier Paradise instead.
- Check MeteoSwiss and Meteotrentino daily
- Alpine weather changes fast -- always carry rain gear
- Lightning protocol: descend ridgelines immediately, avoid isolated trees
### Emergency Meeting Points
\`\`\`
Chamonix: Place Balmat (town center)
Zermatt: Bahnhofplatz (train station)
Dolomites: Rifugio Auronzo parking lot
\`\`\``,
content_plain: "Emergency contacts for France, Switzerland, and Italy. Insurance details, altitude sickness protocol, weather contingency plans, and emergency meeting points.",
type: "NOTE", tags: ["safety", "emergency", "contacts"], is_pinned: false,
created_at: new Date(now - 7 * day).toISOString(), updated_at: new Date(now - 6 * hour).toISOString(),
},
{
id: "demo-note-6", title: "Photo Spots & Creative Plan",
content: `## Photo Spots & Creative Plan
### Must-Capture Locations
1. **Lac Blanc** -- Reflection of Mont Blanc at sunrise. Arrive by 5:30am. Tripod essential.
2. **Gornergrat Panorama** -- 360-degree view with Matterhorn. Golden hour is best.
3. **Tre Cime from Rifugio Locatelli** -- The iconic three peaks at golden hour. Drone shots here.
4. **Seceda Ridgeline** -- Dramatic Dolomite spires. Best drone footage location.
5. **Lago di Braies** -- Turquoise water, use ND filters for long exposure reflections.
### Drone Shot List (Liam)
- Tre Cime circular orbit (check wind < 20km/h)
- Seceda ridge reveal shot (low to high)
- Lago di Braies top-down turquoise pattern
- Matterhorn time-lapse from Gornergrat (if weather permits)
### Zine Plan (Maya)
We are making an **Alpine Explorer Zine** after the trip:
- Format: A5 risograph, 50 copies
- Print at Chamonix Print Collective
- Content: best photos, trail notes, hand-drawn maps
- Price: EUR 12 per copy on rCart
> Bring ND filters for long exposure water shots. Pack the circular polarizer for cutting glare on alpine lakes.
### Video Plan
- Daily 30-second clips for trip recap
- Lac Blanc sunrise test footage already uploaded to rTube (3:42)
- Final edit: 5-8 minute highlight reel`,
content_plain: "Photography and creative plan including must-capture locations, drone shot list, zine production details, and video plan.",
type: "NOTE", tags: ["photography", "creative", "planning"], is_pinned: false,
created_at: new Date(now - 5 * day).toISOString(), updated_at: new Date(now - 4 * hour).toISOString(),
}, },
]; ];
const meetingNotes: Note[] = [ const packingNotes: Note[] = [
{ {
id: "demo-note-5", title: "Sprint Planning \u2014 Feb 24", id: "demo-note-7", title: "Packing Checklist",
content: "Discussed module porting progress. Canvas and books done. Next: work, cal, vote...", content: `## Packing Checklist
content_plain: "Discussed module porting progress. Canvas and books done. Next: work, cal, vote...",
type: "NOTE", tags: ["sprint", "planning"], is_pinned: false, ### Footwear
created_at: new Date(now - 4 * day).toISOString(), updated_at: new Date(now - 3 * hour).toISOString(), - [x] Hiking boots (broken in!)
- [ ] Camp sandals / flip-flops
- [ ] Extra laces
### Clothing
- [x] Rain jacket (Gore-Tex)
- [ ] Down jacket for hut nights
- [ ] 3x wool base layers
- [ ] 2x hiking pants
- [ ] Sun hat + warm beanie
- [ ] Gloves (lightweight)
### Gear
- [x] Headlamp + spare batteries
- [ ] Trekking poles (collapsible)
- [x] First aid kit
- [ ] Sunscreen SPF 50
- [ ] Water filter (Sawyer Squeeze)
- [ ] Dry bags (2x)
- [ ] Repair kit (duct tape, zip ties)
### Electronics
- [ ] Camera + 3 batteries
- [ ] Drone + 3 batteries (Liam)
- [x] Power bank (20,000mAh)
- [ ] Universal adapter (EU plugs)
### Documents
- [x] Passports
- [ ] Travel insurance printout
- [x] Hut reservation confirmations
- [ ] Italian drone registration
- [ ] Emergency contacts card
### Food & Water
- [ ] Reusable water bottles (1L each)
- [ ] Trail snacks (energy bars, nuts)
- [ ] Electrolyte tablets
- [ ] Coffee/tea for hut mornings`,
content_plain: "Complete packing checklist organized by category: footwear, clothing, gear, electronics, documents, and food. Includes checked-off items.",
type: "NOTE", tags: ["packing", "gear", "checklist"], is_pinned: true,
created_at: new Date(now - 6 * day).toISOString(), updated_at: new Date(now - hour).toISOString(),
}, },
{ {
id: "demo-note-6", title: "Design Review \u2014 Feb 22", id: "demo-note-8", title: "Food & Cooking Plan",
content: "Reviewed new shell header. Consensus on simplified nav. Action items: finalize color palette.", content: `## Food & Cooking Plan
content_plain: "Reviewed new shell header. Consensus on simplified nav. Action items: finalize color palette.",
type: "NOTE", tags: ["design", "review"], is_pinned: false, ### Hut Meals (Half-Board)
created_at: new Date(now - 6 * day).toISOString(), updated_at: new Date(now - 6 * hour).toISOString(), Lac Blanc and Locatelli include dinner + breakfast. Budget EUR 0 for those nights.
### Self-Catering Days
We have a kitchen in the Chamonix Airbnb and Zermatt apartment.
**Chamonix Grocery Run (Omar)**
- Pasta, rice, couscous
- Cheese, bread, cured meats
- Fresh vegetables
- Coffee, tea, milk
- Trail mix ingredients
> Omar already spent EUR 93 at Chamonix Carrefour. Receipts in rFunds.
### Trail Lunches
Pack these the night before each hike:
- Sandwiches (baguette + cheese + ham)
- Energy bars (2 per person)
- Nuts and dried fruit
- Chocolate (the altitude calls for it)
- 1.5L water minimum
### Special Meals
- **Jul 10**: Fondue at Chez Vrony, Zermatt (won the dinner vote 5-4 over pizza)
- **Jul 18**: Cooking class in Bolzano (South Tyrolean cuisine)
### Dietary Notes
\`\`\`
Maya: Vegetarian (eggs & dairy OK)
Priya: No shellfish allergy
Others: No restrictions
\`\`\``,
content_plain: "Food and cooking plan covering hut meals, self-catering, trail lunches, special restaurant meals, and dietary notes for all travelers.",
type: "NOTE", tags: ["food", "planning", "budget"], is_pinned: false,
created_at: new Date(now - 4 * day).toISOString(), updated_at: new Date(now - 8 * hour).toISOString(),
}, },
{ {
id: "demo-note-7", title: "Community Call \u2014 Feb 20", id: "demo-note-9", title: "Transport & Logistics",
content: "30 participants. Demoed rFunds river view. Positive feedback on enoughness score.", content: `## Transport & Logistics
content_plain: "30 participants. Demoed rFunds river view. Positive feedback on enoughness score.",
type: "NOTE", tags: ["community", "call"], is_pinned: false, ### Getting There
created_at: new Date(now - 8 * day).toISOString(), updated_at: new Date(now - 8 * hour).toISOString(), - **Jul 6**: Fly to Geneva (everyone arrives by 14:00)
}, - Geneva to Chamonix shuttle: EUR 186 for 4 pax (Maya paid, tracked in rFunds)
{
id: "demo-note-8", title: "Infrastructure Sync \u2014 Feb 18", ### Between Destinations
content: "Mailcow migration complete. All 20 domains verified. DKIM keys rotated.", - **Jul 10**: Chamonix to Zermatt
content_plain: "Mailcow migration complete. All 20 domains verified. DKIM keys rotated.", - Option A: Train via Martigny (~3.5h, scenic)
type: "NOTE", tags: ["infra", "mail"], is_pinned: false, - Option B: Drive via Grand St Bernard tunnel (~2.5h)
created_at: new Date(now - 10 * day).toISOString(), updated_at: new Date(now - 10 * hour).toISOString(), - *Decision: Train (Liam prefers scenic route)*
},
{ - **Jul 14**: Zermatt to Dolomites
id: "demo-note-9", title: "Retrospective \u2014 Feb 15", - Bernina Express is 6 hours but spectacular
content: "What went well: EncryptID launch. What to improve: documentation coverage.", - Rental car is 3.5 hours
content_plain: "What went well: EncryptID launch. What to improve: documentation coverage.", - *Under discussion in rForum -- Priya prefers the train*
type: "NOTE", tags: ["retro"], is_pinned: false,
created_at: new Date(now - 13 * day).toISOString(), updated_at: new Date(now - 13 * hour).toISOString(), ### Local Transport
}, - **Chamonix**: Free local bus with guest card
{ - **Zermatt**: Car-free! Electric taxis + Gornergrat railway
id: "demo-note-10", title: "Onboarding Session \u2014 Feb 12", - **Dolomites**: Need rental car or local bus (limited schedule)
content: "Walked 3 new contributors through rSpace setup. Created video guide.",
content_plain: "Walked 3 new contributors through rSpace setup. Created video guide.", ### Getting Home
type: "NOTE", tags: ["onboarding"], is_pinned: false, - **Jul 20**: Drive to Innsbruck airport (~2.5h from Val Gardena)
created_at: new Date(now - 16 * day).toISOString(), updated_at: new Date(now - 16 * hour).toISOString(), - Return flights booked separately
### Important Timetables
\`\`\`
Gornergrat Railway (first): 07:00 from Zermatt
Lac Blanc trailhead shuttle: 06:30 from Chamonix
Seceda gondola (first): 08:30 from Ortisei
\`\`\`
> Sam is researching rail passes -- the Swiss Travel Pass might save us money for the Zermatt segment.`,
content_plain: "Transport and logistics plan covering flights, inter-city transfers, local transport options, return journey, and important timetables.",
type: "NOTE", tags: ["transport", "logistics"], is_pinned: false,
created_at: new Date(now - 9 * day).toISOString(), updated_at: new Date(now - 5 * hour).toISOString(),
}, },
]; ];
const readingNotes: Note[] = [ const itineraryNotes: Note[] = [
{ {
id: "demo-note-11", title: "Governing the Commons", id: "demo-note-10", title: "Full Itinerary -- Alpine Explorer 2026",
content: "Ostrom's 8 principles for managing shared resources. Especially relevant to our governance module.", content: `## Full Itinerary -- Alpine Explorer 2026
content_plain: "Ostrom's 8 principles for managing shared resources. Especially relevant to our governance module.", **Jul 6-20 | France, Switzerland, Italy**
type: "NOTE", tags: ["book", "governance"], is_pinned: false, **Travelers: Maya, Liam, Priya, Omar, Alex, Sam**
created_at: new Date(now - 14 * day).toISOString(), updated_at: new Date(now - day).toISOString(),
### Week 1: Chamonix, France (Jul 6-10)
| Date | Activity | Category |
|------|----------|----------|
| Jul 6 | Fly Geneva, shuttle to Chamonix | Travel |
| Jul 7 | Acclimatization hike -- Lac Blanc | Hike |
| Jul 8 | Via Ferrata -- Aiguille du Midi | Adventure |
| Jul 9 | Rest day / Chamonix town | Rest |
| Jul 10 | Train to Zermatt | Travel |
### Week 2: Zermatt, Switzerland (Jul 10-14)
| Date | Activity | Category |
|------|----------|----------|
| Jul 10 | Arrive Zermatt, settle in | Travel |
| Jul 11 | Gornergrat sunrise hike | Hike |
| Jul 12 | Matterhorn base camp trek | Hike |
| Jul 13 | Paragliding over Zermatt | Adventure |
| Jul 14 | Transfer to Dolomites | Travel |
### Week 3: Dolomites, Italy (Jul 14-20)
| Date | Activity | Category |
|------|----------|----------|
| Jul 14 | Arrive Val Gardena | Travel |
| Jul 15 | Tre Cime di Lavaredo loop | Hike |
| Jul 16 | Lago di Braies kayaking | Adventure |
| Jul 17 | Seceda ridgeline hike | Hike |
| Jul 18 | Cooking class in Bolzano | Culture |
| Jul 19 | Free day -- shopping & packing | Rest |
| Jul 20 | Fly home from Innsbruck | Travel |
> This itinerary is also tracked in rTrips and synced to the shared rCal calendar.`,
content_plain: "Complete day-by-day itinerary for the Alpine Explorer 2026 trip covering three weeks across Chamonix, Zermatt, and the Dolomites.",
type: "NOTE", tags: ["itinerary", "planning"], is_pinned: true,
created_at: new Date(now - 15 * day).toISOString(), updated_at: new Date(now - 2 * hour).toISOString(),
}, },
{ {
id: "demo-note-12", title: "Entangled Life", id: "demo-note-11", title: "Mountain Hut Reservations",
content: "Sheldrake's exploration of fungal networks. The wood wide web metaphor maps perfectly to mesh networks.", content: `## Mountain Hut Reservations
content_plain: "Sheldrake's exploration of fungal networks. The wood wide web metaphor maps perfectly to mesh networks.",
type: "NOTE", tags: ["book", "mycelium"], is_pinned: false, ### Confirmed
created_at: new Date(now - 20 * day).toISOString(), updated_at: new Date(now - 2 * day).toISOString(), - **Refuge du Lac Blanc** (Jul 7)
- 4 beds reserved, half-board
- Confirmation: #LB2026-234
- Check-in after 15:00, dinner at 19:00
- Bring sleeping bag liner (required)
- **Rifugio Locatelli** (Jul 15)
- 4 beds reserved, half-board
- Confirmation: #TRE2026-089
- Famous for Tre Cime sunset views
- Cash only! Bring EUR 55pp
### Waitlisted
- **Hornlihutte** (Matterhorn base, Jul 12)
- Waitlisted -- will know by Jul 1
- If no luck, camp at Zermatt and do a long day hike instead
- Alex is monitoring the cancellation list
### Hut Etiquette Reminders
1. Arrive before 17:00 if possible
2. Remove boots at entrance (bring hut shoes or thick socks)
3. Lights out by 22:00
4. Pack out all trash
5. Tip is appreciated but not required
> Priya handled all the hut bookings. She has the confirmations printed and digital copies in rFiles.`,
content_plain: "Mountain hut reservations with confirmation numbers, check-in details, and hut etiquette reminders. Two confirmed, one waitlisted.",
type: "NOTE", tags: ["accommodation", "hiking"], is_pinned: false,
created_at: new Date(now - 11 * day).toISOString(), updated_at: new Date(now - day).toISOString(),
}, },
{ {
id: "demo-note-13", title: "Doughnut Economics", id: "demo-note-12", title: "Group Decisions & Votes",
content: "Raworth's framework for staying within planetary boundaries while meeting human needs.", content: `## Group Decisions & Votes
content_plain: "Raworth's framework for staying within planetary boundaries while meeting human needs.",
type: "NOTE", tags: ["book", "economics"], is_pinned: false, ### Decided
created_at: new Date(now - 25 * day).toISOString(), updated_at: new Date(now - 3 * day).toISOString(), - **Camera Gear**: DJI Mini 4 Pro (Liam's decision matrix: 8.5/10)
- **First Night Dinner in Zermatt**: Fondue at Chez Vrony (won 5-4 over pizza)
- **Day 5 Activity**: Via Ferrata at Aiguille du Midi (won 7-3 over kayaking)
### Active Votes (in rVote)
- **Zermatt to Dolomites transfer**: Train vs rental car
- Train: 3 votes (Liam, Priya, Sam)
- Car: 2 votes (Maya, Omar)
- Alex: undecided
### Pending Decisions
- Val Gardena accommodation (agriturismo vs apartment)
- Whether to rent the Starlink Mini (EUR 200, needs funding)
- Trip zine print run size (50 vs 100 copies)
### Decision Framework
We are using a simple majority vote for group activities. For expenses over EUR 100, we need consensus (all 6 agree). Individual expenses are each person's choice.
> All votes are tracked in rVote and synced to the canvas.`,
content_plain: "Summary of group decisions made and active votes. Covers camera gear, dining, activities, and pending decisions with the group's voting framework.",
type: "NOTE", tags: ["decisions", "planning"], is_pinned: false,
created_at: new Date(now - 3 * day).toISOString(), updated_at: new Date(now - 3 * hour).toISOString(),
}, },
]; ];
this.demoNotebooks = [ this.demoNotebooks = [
{ {
id: "demo-nb-1", title: "Project Ideas", description: "Ideas for new projects and features", id: "demo-nb-1", title: "Alpine Explorer Planning", description: "Shared knowledge base for our July 2026 trip across France, Switzerland, and Italy",
cover_color: "#6366f1", note_count: "4", updated_at: new Date(now - hour).toISOString(), cover_color: "#f59e0b", note_count: "6", updated_at: new Date(now - hour).toISOString(),
notes: projectNotes, space: "demo", notes: tripPlanningNotes, space: "demo",
} as any, } as any,
{ {
id: "demo-nb-2", title: "Meeting Notes", description: "Team meetings and sync calls", id: "demo-nb-2", title: "Packing & Logistics", description: "Checklists, food plans, and transport details",
cover_color: "#22c55e", note_count: "6", updated_at: new Date(now - 3 * hour).toISOString(), cover_color: "#22c55e", note_count: "3", updated_at: new Date(now - hour).toISOString(),
notes: meetingNotes, space: "demo", notes: packingNotes, space: "demo",
} as any, } as any,
{ {
id: "demo-nb-3", title: "Reading Journal", description: "Books, articles, and reflections", id: "demo-nb-3", title: "Itinerary & Decisions", description: "Day-by-day schedule, hut reservations, and group votes",
cover_color: "#f59e0b", note_count: "3", updated_at: new Date(now - day).toISOString(), cover_color: "#6366f1", note_count: "3", updated_at: new Date(now - 2 * hour).toISOString(),
notes: readingNotes, space: "demo", notes: itineraryNotes, space: "demo",
} as any, } as any,
]; ];

View File

@ -28,90 +28,77 @@ class FolkTripsPlanner extends HTMLElement {
private loadDemoData() { private loadDemoData() {
this.trips = [ this.trips = [
{ id: "t1", title: "Berlin Maker Week", status: "PLANNING", start_date: "2026-04-14", end_date: "2026-04-20", budget_total: "2500", total_spent: "850", destination_count: 3, description: "Visiting makerspaces and cosmolocal print providers across Berlin" }, { id: "alpine-2026", title: "Alpine Explorer 2026", status: "PLANNING", start_date: "2026-07-06", end_date: "2026-07-20", budget_total: "4500", total_spent: "1203", destination_count: 3, description: "15-day adventure through Chamonix (France) \u2192 Zermatt (Switzerland) \u2192 Dolomites (Italy)" }
{ id: "t2", title: "Mediterranean Commons Tour", status: "BOOKED", start_date: "2026-06-01", end_date: "2026-06-15", budget_total: "4000", total_spent: "3200", destination_count: 4, description: "Connecting with commons communities in Barcelona, Marseille, and Athens" }
]; ];
this.render(); this.render();
} }
private getDemoTripDetail(id: string): any { private getDemoTripDetail(id: string): any {
if (id === "t1") {
return { return {
id: "t1", id: "alpine-2026",
title: "Berlin Maker Week", title: "Alpine Explorer 2026",
status: "PLANNING", status: "PLANNING",
start_date: "2026-04-14", start_date: "2026-07-06",
end_date: "2026-04-20", end_date: "2026-07-20",
budget_total: "2500", budget_total: "4500",
description: "Visiting makerspaces and cosmolocal print providers across Berlin", description: "15-day adventure through Chamonix (France) \u2192 Zermatt (Switzerland) \u2192 Dolomites (Italy). 6 explorers, 3 countries, endless peaks.",
destinations: [ destinations: [
{ id: "d1", name: "Druckwerkstatt Berlin", country: "Germany", arrival_date: "2026-04-14" }, { id: "d1", name: "Chamonix", country: "France", arrival_date: "2026-07-06" },
{ id: "d2", name: "c-base Hackerspace", country: "Germany", arrival_date: "2026-04-16" }, { id: "d2", name: "Zermatt", country: "Switzerland", arrival_date: "2026-07-12" },
{ id: "d3", name: "Fab Lab Berlin", country: "Germany", arrival_date: "2026-04-18" } { id: "d3", name: "Dolomites", country: "Italy", arrival_date: "2026-07-17" }
], ],
itinerary: [ itinerary: [
{ id: "i1", title: "Visit Druckwerkstatt", category: "ACTIVITY", date: "2026-04-14", start_time: "10:00" }, { id: "i1", title: "Fly Geneva \u2192 Chamonix shuttle", category: "TRANSPORT", date: "2026-07-06", start_time: "10:00" },
{ id: "i2", title: "Print workshop", category: "WORKSHOP", date: "2026-04-15", start_time: "09:00" }, { id: "i2", title: "Acclimatization hike \u2014 Lac Blanc", category: "ACTIVITY", date: "2026-07-07", start_time: "07:00" },
{ id: "i3", title: "c-base tour", category: "ACTIVITY", date: "2026-04-16", start_time: "14:00" }, { id: "i3", title: "Via Ferrata \u2014 Aiguille du Midi", category: "ACTIVITY", date: "2026-07-08", start_time: "08:00" },
{ id: "i4", title: "Fab Lab session", category: "WORKSHOP", date: "2026-04-18", start_time: "10:00" }, { id: "i4", title: "Rest day / Chamonix town", category: "FREE_TIME", date: "2026-07-09", start_time: "10:00" },
{ id: "i5", title: "Team dinner", category: "SOCIAL", date: "2026-04-19", start_time: "19:00" } { id: "i5", title: "Group dinner \u2014 La Cabane", category: "MEAL", date: "2026-07-09", start_time: "19:00" },
{ id: "i6", title: "Mont Blanc viewpoint hike", category: "ACTIVITY", date: "2026-07-10", start_time: "06:30" },
{ id: "i7", title: "Farewell lunch in Chamonix", category: "MEAL", date: "2026-07-11", start_time: "12:00" },
{ id: "i8", title: "Train to Zermatt via Glacier Express", category: "TRANSPORT", date: "2026-07-12", start_time: "08:00" },
{ id: "i9", title: "Gornergrat sunrise hike", category: "ACTIVITY", date: "2026-07-13", start_time: "05:30" },
{ id: "i10", title: "Matterhorn base camp trek", category: "ACTIVITY", date: "2026-07-14", start_time: "07:00" },
{ id: "i11", title: "Paragliding over Zermatt", category: "ACTIVITY", date: "2026-07-15", start_time: "10:00" },
{ id: "i12", title: "Fondue dinner \u2014 Chez Vrony", category: "MEAL", date: "2026-07-15", start_time: "19:30" },
{ id: "i13", title: "Transfer to Dolomites", category: "TRANSPORT", date: "2026-07-16", start_time: "09:00" },
{ id: "i14", title: "Tre Cime di Lavaredo loop", category: "ACTIVITY", date: "2026-07-17", start_time: "07:00" },
{ id: "i15", title: "Lago di Braies kayaking", category: "ACTIVITY", date: "2026-07-18", start_time: "09:00" },
{ id: "i16", title: "Cooking class in Bolzano", category: "ACTIVITY", date: "2026-07-19", start_time: "11:00" },
{ id: "i17", title: "Free day \u2014 shopping & packing", category: "FREE_TIME", date: "2026-07-19", start_time: "14:00" },
{ id: "i18", title: "Fly home from Innsbruck", category: "TRANSPORT", date: "2026-07-20", start_time: "12:00" }
], ],
bookings: [ bookings: [
{ id: "bk1", type: "HOTEL", provider: "Hotel Amano", confirmation_number: "AMN-29381", cost: "560" }, { id: "bk1", type: "FLIGHT", provider: "easyJet \u2014 Geneva", confirmation_number: "EZY-20260706-ALP", cost: "890" },
{ id: "bk2", type: "TRANSPORT", provider: "Deutsche Bahn", confirmation_number: "DB-773920", cost: "85" } { id: "bk2", type: "TRANSPORT", provider: "Glacier Express", confirmation_number: "GEX-445920", cost: "240" },
{ id: "bk3", type: "ACCOMMODATION", provider: "Refuge du Lac Blanc", confirmation_number: "LB2026-234", cost: "320" },
{ id: "bk4", type: "ACCOMMODATION", provider: "Hotel Matterhorn Focus, Zermatt", confirmation_number: "MF-88201", cost: "780" },
{ id: "bk5", type: "ACCOMMODATION", provider: "Rifugio Locatelli, Dolomites", confirmation_number: "TRE2026-089", cost: "280" },
{ id: "bk6", type: "ACTIVITY", provider: "Paragliding Zermatt (tandem x6)", confirmation_number: "PGZ-1120", cost: "1080" }
], ],
expenses: [ expenses: [
{ id: "e1", category: "TRANSPORT", description: "Flights", amount: "450", date: "2026-04-14" }, { id: "e1", category: "TRANSPORT", description: "Geneva \u2192 Chamonix shuttle (6 pax)", amount: "186", date: "2026-07-06" },
{ id: "e2", category: "ACCOMMODATION", description: "Accommodation", amount: "280", date: "2026-04-14" }, { id: "e2", category: "ACCOMMODATION", description: "Mountain hut reservations (3 nights)", amount: "420", date: "2026-07-07" },
{ id: "e3", category: "ACTIVITY", description: "Workshop fee", amount: "120", date: "2026-04-15" } { id: "e3", category: "ACTIVITY", description: "Via Ferrata gear rental (6 sets)", amount: "216", date: "2026-07-08" },
{ id: "e4", category: "FOOD", description: "Groceries \u2014 Chamonix Carrefour", amount: "93", date: "2026-07-06" },
{ id: "e5", category: "ACTIVITY", description: "Paragliding deposit (4 of 6 booked)", amount: "288", date: "2026-07-13" }
], ],
packing: [ packing: [
{ id: "pk1", name: "Laptop", category: "TECH", quantity: 1, packed: true }, { id: "pk1", name: "Hiking boots (broken in)", category: "FOOTWEAR", quantity: 1, packed: true },
{ id: "pk2", name: "Notebook", category: "SUPPLIES", quantity: 1, packed: true }, { id: "pk2", name: "Rain jacket", category: "CLOTHING", quantity: 1, packed: true },
{ id: "pk3", name: "USB drives", category: "TECH", quantity: 3, packed: false }, { id: "pk3", name: "Trekking poles", category: "GEAR", quantity: 1, packed: false },
{ id: "pk4", name: "Camera", category: "TECH", quantity: 1, packed: false }, { id: "pk4", name: "Headlamp + batteries", category: "GEAR", quantity: 1, packed: true },
{ id: "pk5", name: "Adapters", category: "TECH", quantity: 2, packed: false } { id: "pk5", name: "Sunscreen SPF 50", category: "PERSONAL", quantity: 1, packed: false },
] { id: "pk6", name: "Water filter", category: "GEAR", quantity: 1, packed: false },
}; { id: "pk7", name: "First aid kit", category: "SAFETY", quantity: 1, packed: true },
} { id: "pk8", name: "Passport + travel insurance", category: "DOCUMENTS", quantity: 1, packed: true }
return {
id: "t2",
title: "Mediterranean Commons Tour",
status: "BOOKED",
start_date: "2026-06-01",
end_date: "2026-06-15",
budget_total: "4000",
description: "Connecting with commons communities in Barcelona, Marseille, and Athens",
destinations: [
{ id: "d4", name: "Fab Lab Barcelona", country: "Spain", arrival_date: "2026-06-01" },
{ id: "d5", name: "La Coop des Communs", country: "France", arrival_date: "2026-06-05" },
{ id: "d6", name: "P2P Foundation Athens", country: "Greece", arrival_date: "2026-06-09" },
{ id: "d7", name: "Synergatika Cooperative", country: "Greece", arrival_date: "2026-06-12" }
], ],
itinerary: [ collaborators: [
{ id: "i6", title: "Fab Lab Barcelona tour", category: "ACTIVITY", date: "2026-06-01", start_time: "10:00" }, { name: "Alex", role: "organizer" },
{ id: "i7", title: "Commons workshop", category: "WORKSHOP", date: "2026-06-02", start_time: "09:00" }, { name: "Sam", role: "photographer" },
{ id: "i8", title: "Marseille meetup", category: "SOCIAL", date: "2026-06-05", start_time: "18:00" }, { name: "Jordan", role: "logistics" },
{ id: "i9", title: "P2P Foundation visit", category: "ACTIVITY", date: "2026-06-09", start_time: "11:00" }, { name: "Riley", role: "navigator" },
{ id: "i10", title: "Cooperative workshop", category: "WORKSHOP", date: "2026-06-12", start_time: "10:00" } { name: "Casey", role: "gear lead" },
], { name: "Morgan", role: "safety" }
bookings: [
{ id: "bk3", type: "TRANSPORT", provider: "Vueling Airlines", confirmation_number: "VY-482910", cost: "320" },
{ id: "bk4", type: "HOTEL", provider: "Hotel Casa Bonay", confirmation_number: "CB-11204", cost: "780" },
{ id: "bk5", type: "TRANSPORT", provider: "Eurostar", confirmation_number: "ES-556271", cost: "190" }
],
expenses: [
{ id: "e4", category: "TRANSPORT", description: "Flights and trains", amount: "1200", date: "2026-06-01" },
{ id: "e5", category: "ACCOMMODATION", description: "Hotels and hostels", amount: "1400", date: "2026-06-01" },
{ id: "e6", category: "FOOD", description: "Meals and groceries", amount: "450", date: "2026-06-01" },
{ id: "e7", category: "ACTIVITY", description: "Workshop fees", amount: "150", date: "2026-06-02" }
],
packing: [
{ id: "pk6", name: "Laptop", category: "TECH", quantity: 1, packed: true },
{ id: "pk7", name: "Sunscreen", category: "PERSONAL", quantity: 1, packed: true },
{ id: "pk8", name: "Phrasebooks", category: "SUPPLIES", quantity: 2, packed: false },
{ id: "pk9", name: "Camera", category: "TECH", quantity: 1, packed: true },
{ id: "pk10", name: "Power bank", category: "TECH", quantity: 1, packed: false }
] ]
}; };
} }

View File

@ -8,9 +8,12 @@ interface VoteSpace {
slug: string; slug: string;
name: string; name: string;
description: string; description: string;
visibility: string;
promotion_threshold: number; promotion_threshold: number;
voting_period_days: number; voting_period_days: number;
credits_per_day: number; credits_per_day: number;
max_credits: number;
starting_credits: number;
} }
interface Proposal { interface Proposal {
@ -51,7 +54,17 @@ class FolkVoteDashboard extends HTMLElement {
private loadDemoData() { private loadDemoData() {
this.spaces = [ this.spaces = [
{ slug: "rspace", name: "rSpace Governance", description: "Community proposals for the rSpace ecosystem", promotion_threshold: 100, voting_period_days: 7, credits_per_day: 10 } {
slug: "community",
name: "Community Governance",
description: "Proposals for the rSpace ecosystem",
visibility: "public_read",
promotion_threshold: 100,
voting_period_days: 7,
credits_per_day: 10,
max_credits: 500,
starting_credits: 50,
},
]; ];
this.selectedSpace = this.spaces[0]; this.selectedSpace = this.spaces[0];
this.view = "proposals"; this.view = "proposals";
@ -59,10 +72,61 @@ class FolkVoteDashboard extends HTMLElement {
const now = Date.now(); const now = Date.now();
const day = 86400000; const day = 86400000;
this.proposals = [ this.proposals = [
{ id: "p1", title: "Fund local-first infrastructure sprint", description: "Allocate 5000 DAI for a 2-week sprint on Automerge integration", status: "RANKING", score: 73, vote_count: "12", final_yes: 0, final_no: 0, final_abstain: 0, created_at: new Date(now - 2 * day).toISOString(), voting_ends_at: null }, {
{ id: "p2", title: "Add dark mode to all rApps", description: "Standardize dark theme tokens across the ecosystem", status: "VOTING", score: 100, vote_count: "8", final_yes: 14, final_no: 3, final_abstain: 2, created_at: new Date(now - 5 * day).toISOString(), voting_ends_at: new Date(now + 3 * day).toISOString() }, id: "p1",
{ id: "p3", title: "Create onboarding tutorial series", description: "Video walkthrough series for new community members", status: "PASSED", score: 100, vote_count: "15", final_yes: 22, final_no: 4, final_abstain: 1, created_at: new Date(now - 14 * day).toISOString(), voting_ends_at: new Date(now - 3 * day).toISOString() }, title: "Implement real-time collaboration in rNotes",
{ id: "p4", title: "Migrate to paid hosting tier", description: "Move from shared to dedicated infrastructure", status: "FAILED", score: 100, vote_count: "11", final_yes: 5, final_no: 18, final_abstain: 3, created_at: new Date(now - 14 * day).toISOString(), voting_ends_at: new Date(now - 5 * day).toISOString() } description: "Use Automerge CRDTs (already in the stack) to enable simultaneous editing of notes, similar to how rSpace canvas works.",
status: "RANKING",
score: 72,
vote_count: "9",
final_yes: 0, final_no: 0, final_abstain: 0,
created_at: new Date(now - 3 * day).toISOString(),
voting_ends_at: null,
},
{
id: "p2",
title: "Add dark mode across all r* modules",
description: "Implement a consistent dark theme with a toggle in shell.css. Use CSS custom properties for theming so each module inherits automatically.",
status: "RANKING",
score: 45,
vote_count: "6",
final_yes: 0, final_no: 0, final_abstain: 0,
created_at: new Date(now - 5 * day).toISOString(),
voting_ends_at: null,
},
{
id: "p3",
title: "Adopt cosmolocal print-on-demand for all merch",
description: "Route all merchandise orders through the provider registry to find the closest printer. Reduces shipping emissions and supports local economies.",
status: "VOTING",
score: 105,
vote_count: "14",
final_yes: 5, final_no: 2, final_abstain: 0,
created_at: new Date(now - 10 * day).toISOString(),
voting_ends_at: new Date(now + 5 * day).toISOString(),
},
{
id: "p4",
title: "Use EncryptID passkeys for all authentication",
description: "Standardize on WebAuthn passkeys via EncryptID across the entire r* ecosystem. One passkey, all apps.",
status: "PASSED",
score: 150,
vote_count: "17",
final_yes: 12, final_no: 3, final_abstain: 2,
created_at: new Date(now - 21 * day).toISOString(),
voting_ends_at: new Date(now - 7 * day).toISOString(),
},
{
id: "p5",
title: "Switch from PostgreSQL to SQLite for simpler deployment",
description: "Evaluate replacing PostgreSQL with SQLite for modules that don't need concurrent writes.",
status: "FAILED",
score: 30,
vote_count: "11",
final_yes: 2, final_no: 8, final_abstain: 1,
created_at: new Date(now - 18 * day).toISOString(),
voting_ends_at: new Date(now - 4 * day).toISOString(),
},
]; ];
this.render(); this.render();
} }
@ -120,7 +184,18 @@ class FolkVoteDashboard extends HTMLElement {
private async castVote(proposalId: string, weight: number) { private async castVote(proposalId: string, weight: number) {
if (this.space === "demo") { if (this.space === "demo") {
const p = this.proposals.find(p => p.id === proposalId); const p = this.proposals.find(p => p.id === proposalId);
if (p) { p.score += weight; this.selectedProposal = p; } if (p) {
p.score += weight;
p.vote_count = String(parseInt(p.vote_count) + 1);
// Auto-promote if score reaches threshold (matches server-side behavior)
const threshold = this.selectedSpace?.promotion_threshold || 100;
if (p.score >= threshold && p.status === "RANKING") {
p.status = "VOTING";
const votingDays = this.selectedSpace?.voting_period_days || 7;
p.voting_ends_at = new Date(Date.now() + votingDays * 86400000).toISOString();
}
this.selectedProposal = p;
}
this.render(); this.render();
return; return;
} }
@ -251,18 +326,30 @@ class FolkVoteDashboard extends HTMLElement {
${this.spaces.length === 0 ? '<div class="empty">No voting spaces yet. Create one to get started.</div>' : ""} ${this.spaces.length === 0 ? '<div class="empty">No voting spaces yet. Create one to get started.</div>' : ""}
${this.spaces.map((s) => ` ${this.spaces.map((s) => `
<div class="card" data-space="${s.slug}"> <div class="card" data-space="${s.slug}">
<div style="display:flex;justify-content:space-between;align-items:center">
<div class="card-title">${this.esc(s.name)}</div> <div class="card-title">${this.esc(s.name)}</div>
<span class="badge" style="background:rgba(129,140,248,0.15);color:#818cf8">${s.visibility === "public_read" ? "Public" : s.visibility}</span>
</div>
<div class="card-desc">${this.esc(s.description || "")}</div> <div class="card-desc">${this.esc(s.description || "")}</div>
<div class="card-meta"> <div class="card-meta">
<span>Threshold: ${s.promotion_threshold}</span> <span>Threshold: ${s.promotion_threshold}</span>
<span>Voting: ${s.voting_period_days}d</span> <span>Voting: ${s.voting_period_days}d</span>
<span>${s.credits_per_day} credits/day</span> <span>${s.credits_per_day} credits/day</span>
<span>Max: ${s.max_credits}</span>
<span>Start: ${s.starting_credits}</span>
</div> </div>
</div> </div>
`).join("")} `).join("")}
`; `;
} }
private relativeTime(dateStr: string): string {
const daysAgo = Math.floor((Date.now() - new Date(dateStr).getTime()) / 86400000);
if (daysAgo === 0) return "today";
if (daysAgo === 1) return "1 day ago";
return `${daysAgo} days ago`;
}
private renderProposals(): string { private renderProposals(): string {
const s = this.selectedSpace!; const s = this.selectedSpace!;
return ` return `
@ -282,13 +369,26 @@ class FolkVoteDashboard extends HTMLElement {
<div class="score-bar"> <div class="score-bar">
<div class="score-fill" style="width:${Math.min(100, (p.score / (s.promotion_threshold || 100)) * 100)}%;background:${this.getStatusColor(p.status)}"></div> <div class="score-fill" style="width:${Math.min(100, (p.score / (s.promotion_threshold || 100)) * 100)}%;background:${this.getStatusColor(p.status)}"></div>
</div> </div>
<div class="card-meta"><span>Score: ${Math.round(p.score)} / ${s.promotion_threshold}</span></div> <div class="card-meta">
<span>Score: ${Math.round(p.score)} / ${s.promotion_threshold}</span>
<span>${p.vote_count} votes</span>
<span>${this.relativeTime(p.created_at)}</span>
</div>
` : ""} ` : ""}
${p.status === "VOTING" || p.status === "PASSED" || p.status === "FAILED" ? ` ${p.status === "VOTING" ? `
<div class="card-meta"> <div class="card-meta">
<span style="color:#22c55e">Yes: ${p.final_yes}</span> <span style="color:#22c55e">Yes: ${p.final_yes}</span>
<span style="color:#ef4444">No: ${p.final_no}</span> <span style="color:#ef4444">No: ${p.final_no}</span>
<span style="color:#f59e0b">Abstain: ${p.final_abstain}</span> <span style="color:#f59e0b">Abstain: ${p.final_abstain}</span>
${p.voting_ends_at ? `<span>Ends: ${new Date(p.voting_ends_at).toLocaleDateString()}</span>` : ""}
</div>
` : ""}
${p.status === "PASSED" || p.status === "FAILED" ? `
<div class="card-meta">
<span style="color:#22c55e">Yes: ${p.final_yes}</span>
<span style="color:#ef4444">No: ${p.final_no}</span>
<span style="color:#f59e0b">Abstain: ${p.final_abstain}</span>
<span>${this.relativeTime(p.created_at)}</span>
</div> </div>
` : ""} ` : ""}
</div> </div>
@ -298,6 +398,11 @@ class FolkVoteDashboard extends HTMLElement {
private renderProposal(): string { private renderProposal(): string {
const p = this.selectedProposal!; const p = this.selectedProposal!;
const threshold = this.selectedSpace?.promotion_threshold || 100;
const created = new Date(p.created_at);
const daysAgo = Math.floor((Date.now() - created.getTime()) / 86400000);
const ageText = daysAgo === 0 ? "today" : daysAgo === 1 ? "1 day ago" : `${daysAgo} days ago`;
return ` return `
<div class="rapp-nav"> <div class="rapp-nav">
<button class="rapp-nav__back" data-back="proposals"> Proposals</button> <button class="rapp-nav__back" data-back="proposals"> Proposals</button>
@ -306,23 +411,34 @@ class FolkVoteDashboard extends HTMLElement {
</div> </div>
<div class="card" style="cursor:default"> <div class="card" style="cursor:default">
<div class="card-desc" style="font-size:14px;color:#ccc;margin-bottom:12px">${this.esc(p.description || "No description")}</div> <div class="card-desc" style="font-size:14px;color:#ccc;margin-bottom:12px">${this.esc(p.description || "No description")}</div>
<div class="card-meta" style="margin-bottom:12px">
<span>${p.vote_count} votes</span>
<span>Created ${ageText}</span>
</div>
${p.status === "RANKING" ? ` ${p.status === "RANKING" ? `
<div style="margin-bottom:8px;font-size:13px;color:#888">Conviction score: <strong style="color:#3b82f6">${Math.round(p.score)}</strong></div> <div style="margin-bottom:8px;font-size:13px;color:#888">
<div class="score-bar"> Conviction score: <strong style="color:#3b82f6">${Math.round(p.score)}</strong> / ${threshold}
<div class="score-fill" style="width:${Math.min(100, (p.score / 100) * 100)}%;background:#3b82f6"></div> </div>
<div class="score-bar">
<div class="score-fill" style="width:${Math.min(100, (p.score / threshold) * 100)}%;background:#3b82f6"></div>
</div>
<div style="font-size:12px;color:#666;margin-top:4px">
Needs ${threshold} to advance to voting (quadratic cost: weight&sup2; credits)
</div> </div>
<div style="font-size:12px;color:#666;margin-top:4px">Needs 100 to advance to voting</div>
<div class="vote-controls"> <div class="vote-controls">
${[1, 2, 3, 5].map((w) => ` ${[1, 2, 3, 5].map((w) => `
<button class="vote-btn" data-vote-weight="${w}"> <button class="vote-btn" data-vote-weight="${w}">
Vote +${w} (${w * w} credits) +${w} (${w * w} credits)
</button> </button>
`).join("")} `).join("")}
</div> </div>
` : ""} ` : ""}
${p.status === "VOTING" ? ` ${p.status === "VOTING" ? `
<div style="margin-bottom:8px;font-size:13px;color:#888">
Promoted with conviction score <strong style="color:#f59e0b">${Math.round(p.score)}</strong> now in ${this.selectedSpace?.voting_period_days || 7}-day final vote
</div>
<div class="tally"> <div class="tally">
<div class="tally-item"><div class="tally-value" style="color:#22c55e">${p.final_yes}</div><div class="tally-label">Yes</div></div> <div class="tally-item"><div class="tally-value" style="color:#22c55e">${p.final_yes}</div><div class="tally-label">Yes</div></div>
<div class="tally-item"><div class="tally-value" style="color:#ef4444">${p.final_no}</div><div class="tally-label">No</div></div> <div class="tally-item"><div class="tally-value" style="color:#ef4444">${p.final_no}</div><div class="tally-label">No</div></div>
@ -337,6 +453,10 @@ class FolkVoteDashboard extends HTMLElement {
` : ""} ` : ""}
${p.status === "PASSED" || p.status === "FAILED" ? ` ${p.status === "PASSED" || p.status === "FAILED" ? `
<div style="margin-bottom:8px;font-size:13px;color:#888">
Final result: <strong style="color:${this.getStatusColor(p.status)}">${p.status}</strong>
(conviction score was ${Math.round(p.score)})
</div>
<div class="tally"> <div class="tally">
<div class="tally-item"><div class="tally-value" style="color:#22c55e">${p.final_yes}</div><div class="tally-label">Yes</div></div> <div class="tally-item"><div class="tally-value" style="color:#22c55e">${p.final_yes}</div><div class="tally-label">Yes</div></div>
<div class="tally-item"><div class="tally-value" style="color:#ef4444">${p.final_no}</div><div class="tally-label">No</div></div> <div class="tally-item"><div class="tally-value" style="color:#ef4444">${p.final_no}</div><div class="tally-label">No</div></div>
@ -354,7 +474,11 @@ class FolkVoteDashboard extends HTMLElement {
const slug = (el as HTMLElement).dataset.space!; const slug = (el as HTMLElement).dataset.space!;
this.selectedSpace = this.spaces.find((s) => s.slug === slug) || null; this.selectedSpace = this.spaces.find((s) => s.slug === slug) || null;
this.view = "proposals"; this.view = "proposals";
if (this.space === "demo") {
this.render();
} else {
this.loadProposals(slug); this.loadProposals(slug);
}
}); });
}); });
@ -363,7 +487,12 @@ class FolkVoteDashboard extends HTMLElement {
el.addEventListener("click", () => { el.addEventListener("click", () => {
const id = (el as HTMLElement).dataset.proposal!; const id = (el as HTMLElement).dataset.proposal!;
this.view = "proposal"; this.view = "proposal";
if (this.space === "demo") {
this.selectedProposal = this.proposals.find((p) => p.id === id) || null;
this.render();
} else {
this.loadProposal(id); this.loadProposal(id);
}
}); });
}); });

View File

@ -41,10 +41,10 @@ async function seedDemoIfEmpty() {
); );
const userId = user[0].id; const userId = user[0].id;
// Create voting space // Create voting space (matches standalone rVote settings)
await sql.unsafe( await sql.unsafe(
`INSERT INTO rvote.spaces (slug, name, description, owner_did, visibility, promotion_threshold) `INSERT INTO rvote.spaces (slug, name, description, owner_did, visibility, promotion_threshold, voting_period_days, credits_per_day, max_credits, starting_credits)
VALUES ('community', 'Community Governance', 'Proposals for the rSpace ecosystem', 'did:demo:seed', 'public', 100)` VALUES ('community', 'Community Governance', 'Proposals for the rSpace ecosystem', 'did:demo:seed', 'public_read', 100, 7, 10, 500, 50)`
); );
// Seed proposals in various states // Seed proposals in various states

View File

@ -66,12 +66,16 @@ class FolkWalletViewer extends HTMLElement {
this.address = "0x29567BdBcC92aCF37AC6B56B69180857bB69f7D1"; this.address = "0x29567BdBcC92aCF37AC6B56B69180857bB69f7D1";
this.detectedChains = [ this.detectedChains = [
{ chainId: "1", name: "Ethereum", prefix: "eth", color: "#627eea" }, { chainId: "1", name: "Ethereum", prefix: "eth", color: "#627eea" },
{ chainId: "100", name: "Gnosis Chain", prefix: "gno", color: "#04795b" }, { chainId: "10", name: "Optimism", prefix: "oeth", color: "#ff0420" },
{ chainId: "137", name: "Polygon", prefix: "matic", color: "#8247e5" }, { chainId: "100", name: "Gnosis", prefix: "gno", color: "#04795b" },
{ chainId: "137", name: "Polygon", prefix: "pol", color: "#8247e5" },
{ chainId: "8453", name: "Base", prefix: "base", color: "#0052ff" },
{ chainId: "42161", name: "Arbitrum", prefix: "arb1", color: "#28a0f0" },
{ chainId: "43114", name: "Avalanche", prefix: "avax", color: "#e84142" },
]; ];
this.selectedChain = "100"; this.selectedChain = "100";
this.balances = [ this.balances = [
{ tokenAddress: null, token: { name: "xDAI", symbol: "XDAI", decimals: 18 }, balance: "45230000000000000000000", fiatBalance: "45230", fiatConversion: "1" }, { tokenAddress: null, token: { name: "xDAI", symbol: "xDAI", decimals: 18 }, balance: "45230000000000000000000", fiatBalance: "45230", fiatConversion: "1" },
{ tokenAddress: "0x5dF8339c5E282ee48c0c7cE252A7842F74e378b2", token: { name: "Token Engineering Commons", symbol: "TEC", decimals: 18 }, balance: "1250000000000000000000000", fiatBalance: "12500", fiatConversion: "0.01" }, { tokenAddress: "0x5dF8339c5E282ee48c0c7cE252A7842F74e378b2", token: { name: "Token Engineering Commons", symbol: "TEC", decimals: 18 }, balance: "1250000000000000000000000", fiatBalance: "12500", fiatConversion: "0.01" },
{ tokenAddress: "0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1", token: { name: "Wrapped Ether", symbol: "WETH", decimals: 18 }, balance: "8500000000000000000", fiatBalance: "28050", fiatConversion: "3300" }, { tokenAddress: "0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1", token: { name: "Wrapped Ether", symbol: "WETH", decimals: 18 }, balance: "8500000000000000000", fiatBalance: "28050", fiatConversion: "3300" },
{ tokenAddress: "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", token: { name: "USD Coin", symbol: "USDC", decimals: 6 }, balance: "15750000000", fiatBalance: "15750", fiatConversion: "1" }, { tokenAddress: "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83", token: { name: "USD Coin", symbol: "USDC", decimals: 6 }, balance: "15750000000", fiatBalance: "15750", fiatConversion: "1" },

View File

@ -32,21 +32,21 @@ class FolkWorkBoard extends HTMLElement {
private loadDemoData() { private loadDemoData() {
this.isDemo = true; this.isDemo = true;
this.workspaces = [{ slug: "rspace-dev", name: "rSpace Development", icon: "\u{1F680}", task_count: 11, member_count: 4 }]; this.workspaces = [{ slug: "rspace-dev", name: "rSpace Development", icon: "\u{1F680}", task_count: 11, member_count: 2 }];
this.view = "board"; this.view = "board";
this.workspaceSlug = "rspace-dev"; this.workspaceSlug = "rspace-dev";
this.tasks = [ this.tasks = [
{ id: "d1", title: "Add dark theme toggle", status: "TODO", priority: "NORMAL", labels: ["frontend"] }, { id: "d1", title: "Add dark mode toggle to settings page", status: "TODO", priority: "MEDIUM", labels: ["feature"], assignee: "Alice" },
{ id: "d2", title: "Write API docs", status: "TODO", priority: "NORMAL", labels: ["backend", "docs"] }, { id: "d2", title: "Write API documentation for rPubs endpoints", status: "TODO", priority: "LOW", labels: ["docs"], assignee: "Bob" },
{ id: "d3", title: "Set up CI pipeline", status: "TODO", priority: "NORMAL", labels: ["devops"] }, { id: "d3", title: "Investigate slow PDF generation on large documents", status: "TODO", priority: "HIGH", labels: ["bug"], assignee: "Alice" },
{ id: "d4", title: "Port canvas to WebGL", status: "IN_PROGRESS", priority: "NORMAL", labels: ["frontend"] }, { id: "d4", title: "Implement file search and filtering in rFiles", status: "IN_PROGRESS", priority: "HIGH", labels: ["feature"], assignee: "Alice" },
{ id: "d5", title: "Implement search API", status: "IN_PROGRESS", priority: "HIGH", labels: ["backend"] }, { id: "d5", title: "Set up SMTP relay for transactional notifications", status: "IN_PROGRESS", priority: "MEDIUM", labels: ["chore"], assignee: "Bob" },
{ id: "d6", title: "Design mobile nav", status: "IN_PROGRESS", priority: "NORMAL", labels: ["frontend", "design"] }, { id: "d6", title: "Add PDF export to rNotes notebooks", status: "REVIEW", priority: "MEDIUM", labels: ["feature"], assignee: "Bob" },
{ id: "d7", title: "Fix WebSocket reconnection", status: "REVIEW", priority: "URGENT", labels: ["backend"] }, { id: "d7", title: "Fix conviction score decay calculation in rVote", status: "REVIEW", priority: "HIGH", labels: ["bug"], assignee: "Alice" },
{ id: "d8", title: "Add rate limiting", status: "REVIEW", priority: "NORMAL", labels: ["backend", "security"] }, { id: "d8", title: "Deploy EncryptID passkey authentication", status: "DONE", priority: "URGENT", labels: ["feature"], assignee: "Alice" },
{ id: "d9", title: "Deploy auth service", status: "DONE", priority: "NORMAL", labels: ["devops"] }, { id: "d9", title: "Set up Cloudflare tunnel for all r* domains", status: "DONE", priority: "HIGH", labels: ["chore"], assignee: "Bob" },
{ id: "d10", title: "Set up monitoring", status: "DONE", priority: "NORMAL", labels: ["devops"] }, { id: "d10", title: "Create cosmolocal provider directory with 6 printers", status: "DONE", priority: "MEDIUM", labels: ["feature"], assignee: "Alice" },
{ id: "d11", title: "Create landing page", status: "DONE", priority: "NORMAL", labels: ["frontend", "design"] }, { id: "d11", title: "Migrate email from Resend to self-hosted Mailcow", status: "DONE", priority: "MEDIUM", labels: ["chore"], assignee: "Bob" },
]; ];
this.render(); this.render();
} }
@ -106,7 +106,7 @@ class FolkWorkBoard extends HTMLElement {
const title = prompt("Task title:"); const title = prompt("Task title:");
if (!title?.trim()) return; if (!title?.trim()) return;
if (this.isDemo) { if (this.isDemo) {
this.tasks.push({ id: `d${Date.now()}`, title: title.trim(), status: "TODO", priority: "NORMAL", labels: [] }); this.tasks.push({ id: `d${Date.now()}`, title: title.trim(), status: "TODO", priority: "MEDIUM", labels: [] });
this.render(); this.render();
return; return;
} }
@ -188,6 +188,8 @@ class FolkWorkBoard extends HTMLElement {
.badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #2a2a3a; color: #aaa; } .badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #2a2a3a; color: #aaa; }
.badge-urgent { background: #3b1111; color: #f87171; } .badge-urgent { background: #3b1111; color: #f87171; }
.badge-high { background: #3b2611; color: #fb923c; } .badge-high { background: #3b2611; color: #fb923c; }
.badge-medium { background: #3b3511; color: #facc15; }
.badge-low { background: #112a3b; color: #60a5fa; }
.move-btns { display: flex; gap: 4px; margin-top: 6px; } .move-btns { display: flex; gap: 4px; margin-top: 6px; }
.move-btn { font-size: 10px; padding: 2px 6px; border-radius: 4px; border: 1px solid #333; background: #16161e; color: #888; cursor: pointer; } .move-btn { font-size: 10px; padding: 2px 6px; border-radius: 4px; border: 1px solid #333; background: #16161e; color: #888; cursor: pointer; }
@ -253,14 +255,18 @@ class FolkWorkBoard extends HTMLElement {
private renderTaskCard(task: any, currentStatus: string): string { private renderTaskCard(task: any, currentStatus: string): string {
const otherStatuses = this.statuses.filter(s => s !== currentStatus); const otherStatuses = this.statuses.filter(s => s !== currentStatus);
const priorityBadge = (p: string) => {
const map: Record<string, string> = { URGENT: "badge-urgent", HIGH: "badge-high", MEDIUM: "badge-medium", LOW: "badge-low" };
return map[p] ? `<span class="badge ${map[p]}">${this.esc(p.toLowerCase())}</span>` : "";
};
return ` return `
<div class="task-card" draggable="true" data-task-id="${task.id}"> <div class="task-card" draggable="true" data-task-id="${task.id}">
<div class="task-title">${this.esc(task.title)}</div> <div class="task-title">${this.esc(task.title)}</div>
<div class="task-meta"> <div class="task-meta">
${task.priority === "URGENT" ? '<span class="badge badge-urgent">URGENT</span>' : ""} ${priorityBadge(task.priority || "")}
${task.priority === "HIGH" ? '<span class="badge badge-high">HIGH</span>' : ""}
${(task.labels || []).map((l: string) => `<span class="badge">${this.esc(l)}</span>`).join("")} ${(task.labels || []).map((l: string) => `<span class="badge">${this.esc(l)}</span>`).join("")}
</div> </div>
${task.assignee ? `<div style="font-size:11px;color:#888;margin-top:4px">${this.esc(task.assignee)}</div>` : ""}
<div class="move-btns"> <div class="move-btns">
${otherStatuses.map(s => `<button class="move-btn" data-move="${task.id}" data-to="${s}">→ ${this.esc(s.replace(/_/g, " ").substring(0, 8))}</button>`).join("")} ${otherStatuses.map(s => `<button class="move-btn" data-move="${task.id}" data-to="${s}">→ ${this.esc(s.replace(/_/g, " ").substring(0, 8))}</button>`).join("")}
</div> </div>

View File

@ -65,7 +65,7 @@ import { photosModule } from "../modules/photos/mod";
import { socialsModule } from "../modules/rsocials/mod"; import { socialsModule } from "../modules/rsocials/mod";
import { spaces } from "./spaces"; import { spaces } from "./spaces";
import { renderShell, renderModuleLanding } from "./shell"; import { renderShell, renderModuleLanding } from "./shell";
import { renderMainLanding } from "./landing"; import { renderMainLanding, renderSpaceDashboard } from "./landing";
import { fetchLandingPage } from "./landing-proxy"; import { fetchLandingPage } from "./landing-proxy";
import { syncServer } from "./sync-instance"; import { syncServer } from "./sync-instance";
import { loadAllDocs } from "./local-first/doc-persistence"; import { loadAllDocs } from "./local-first/doc-persistence";
@ -844,12 +844,12 @@ app.get("/admin", async (c) => {
return c.text("Admin", 200); return c.text("Admin", 200);
}); });
// Space root: /:space → redirect to /:space/rspace // Space root: /:space → space dashboard
app.get("/:space", (c) => { app.get("/:space", (c) => {
const space = c.req.param("space"); const space = c.req.param("space");
// Don't redirect for static file paths // Don't serve dashboard for static file paths
if (space.includes(".")) return c.notFound(); if (space.includes(".")) return c.notFound();
return c.redirect(`/${space}/rspace`); return c.html(renderSpaceDashboard(space, getModuleInfoList()));
}); });
// ── WebSocket types ── // ── WebSocket types ──
@ -1101,9 +1101,11 @@ const server = Bun.serve<WSData>({
if (subdomain) { if (subdomain) {
const pathSegments = url.pathname.split("/").filter(Boolean); const pathSegments = url.pathname.split("/").filter(Boolean);
// Root: redirect to default module (rspace) // Root: show space dashboard
if (pathSegments.length === 0) { if (pathSegments.length === 0) {
return Response.redirect(`${url.protocol}//${host}/rspace`, 302); return new Response(renderSpaceDashboard(subdomain, getModuleInfoList()), {
headers: { "Content-Type": "text/html" },
});
} }
// Global routes pass through without subdomain prefix // Global routes pass through without subdomain prefix

View File

@ -241,6 +241,147 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
</html>`; </html>`;
} }
// ── Space Dashboard ──
export function renderSpaceDashboard(space: string, modules: ModuleInfo[]): string {
const moduleListJSON = JSON.stringify(modules);
const displayName = space === "demo" ? "Demo Space" : space;
const subtitle = space === "demo"
? "Explore the rSpace ecosystem — click any rApp to try it live with sample data."
: `${modules.length} rApps available in this space.`;
const appCards = modules
.map((m) => {
const href = `/${escapeAttr(space)}/${escapeAttr(m.id)}`;
return `
<a href="${href}" class="sd-card">
<div class="sd-card__icon">${m.icon}</div>
<div class="sd-card__body">
<h3 class="sd-card__name">${escapeHtml(m.name)}</h3>
<p class="sd-card__desc">${escapeHtml(m.description)}</p>
</div>
</a>`;
})
.join("\n");
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>">
<title>${escapeHtml(displayName)} | rSpace</title>
<link rel="stylesheet" href="/shell.css">
<style>${MODULE_LANDING_CSS}</style>
<style>${SPACE_DASHBOARD_CSS}</style>
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
<body data-theme="dark">
<header class="rstack-header" data-theme="dark">
<div class="rstack-header__left">
<a href="/" style="display:flex;align-items:center;margin-right:4px"><img src="/favicon.png" alt="rSpace" class="rstack-header__logo"></a>
<rstack-app-switcher current=""></rstack-app-switcher>
<rstack-space-switcher current="${escapeAttr(space)}" name="${escapeAttr(displayName)}"></rstack-space-switcher>
</div>
<div class="rstack-header__center">
<rstack-mi></rstack-mi>
</div>
<div class="rstack-header__right">
<rstack-identity></rstack-identity>
</div>
</header>
<div class="sd-hero">
<h1 class="sd-hero__title">${escapeHtml(displayName)}</h1>
<p class="sd-hero__subtitle">${subtitle}</p>
</div>
<div class="sd-container">
<div class="sd-grid">
${appCards}
</div>
</div>
${space === "demo" ? `
<div class="sd-footer">
<p>Want your own space? <a href="/create-space">Create one</a> it&rsquo;s free and instant.</p>
</div>` : ""}
<script type="module">
import '/shell.js';
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
</script>
</body>
</html>`;
}
const SPACE_DASHBOARD_CSS = `
.sd-hero {
text-align: center;
padding: 100px 1.5rem 2rem;
}
.sd-hero__title {
font-size: 2.25rem; font-weight: 700; margin: 0 0 0.5rem;
background: linear-gradient(135deg, #14b8a6, #22d3ee);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.sd-hero__subtitle {
font-size: 1.05rem; color: #94a3b8; margin: 0;
max-width: 520px; margin: 0 auto; line-height: 1.5;
}
.sd-container {
max-width: 1100px; margin: 0 auto;
padding: 1rem 1.5rem 3rem;
}
.sd-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 0.75rem;
}
.sd-card {
display: flex; align-items: flex-start; gap: 1rem;
padding: 1rem 1.25rem;
background: rgba(255,255,255,0.025);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 0.75rem;
text-decoration: none; color: inherit;
transition: border-color 0.2s, background 0.2s, transform 0.15s;
cursor: pointer;
}
.sd-card:hover {
border-color: rgba(20,184,166,0.35);
background: rgba(20,184,166,0.04);
transform: translateY(-1px);
}
.sd-card__icon {
font-size: 1.75rem; flex-shrink: 0;
width: 2.5rem; height: 2.5rem;
display: flex; align-items: center; justify-content: center;
}
.sd-card__body { min-width: 0; }
.sd-card__name {
font-size: 0.9rem; font-weight: 600; color: #e2e8f0;
margin: 0 0 0.2rem;
}
.sd-card__desc {
font-size: 0.78rem; color: #64748b; margin: 0;
line-height: 1.45;
}
.sd-footer {
text-align: center; padding: 2rem 1.5rem 3rem;
border-top: 1px solid rgba(255,255,255,0.06);
}
.sd-footer p { color: #64748b; font-size: 0.9rem; margin: 0; }
.sd-footer a { color: #14b8a6; text-decoration: none; font-weight: 600; }
.sd-footer a:hover { text-decoration: underline; }
@media (max-width: 480px) {
.sd-grid { grid-template-columns: 1fr; }
.sd-hero__title { font-size: 1.75rem; }
.sd-hero { padding-top: 80px; }
}
`;
const MAIN_LANDING_CSS = ` const MAIN_LANDING_CSS = `
/* Main landing page extras (on top of rl-* utilities) */ /* Main landing page extras (on top of rl-* utilities) */
.main-wordmark { .main-wordmark {