wip bsky client for the web & android
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

init

willow 43473234

+5887
+8
.editorconfig
··· 1 + [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] 2 + charset = utf-8 3 + indent_size = 2 4 + indent_style = space 5 + insert_final_newline = true 6 + trim_trailing_whitespace = true 7 + end_of_line = lf 8 + max_line_length = 100
+1
.gitattributes
··· 1 + * text=auto eol=lf
+36
.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + .DS_Store 12 + dist 13 + dist-ssr 14 + coverage 15 + *.local 16 + 17 + # Editor directories and files 18 + .vscode/* 19 + !.vscode/extensions.json 20 + .idea 21 + *.suo 22 + *.ntvs* 23 + *.njsproj 24 + *.sln 25 + *.sw? 26 + 27 + *.tsbuildinfo 28 + 29 + .eslintcache 30 + 31 + # Cypress 32 + /cypress/videos/ 33 + /cypress/screenshots/ 34 + 35 + # Vitest 36 + __screenshots__/
+6
.prettierrc.json
··· 1 + { 2 + "$schema": "https://json.schemastore.org/prettierrc", 3 + "semi": false, 4 + "singleQuote": true, 5 + "printWidth": 100 6 + }
+9
.vscode/extensions.json
··· 1 + { 2 + "recommendations": [ 3 + "Vue.volar", 4 + "dbaeumer.vscode-eslint", 5 + "EditorConfig.EditorConfig", 6 + "oxc.oxc-vscode", 7 + "esbenp.prettier-vscode" 8 + ] 9 + }
+1
README.md
··· 1 + # scilla
+855
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "workspaces": { 4 + "": { 5 + "name": "scilla", 6 + "dependencies": { 7 + "@atcute/atproto": "^3.1.9", 8 + "@atcute/bluesky": "^3.2.14", 9 + "@atcute/client": "^4.1.1", 10 + "@atcute/identity-resolver": "^1.2.0", 11 + "@atcute/lexicons": "^1.2.5", 12 + "@atcute/oauth-browser-client": "^2.0.3", 13 + "@iconify-prerendered/vue-material-symbols": "^0.28.1755063979", 14 + "pinia": "^3.0.4", 15 + "vue": "^3.5.25", 16 + }, 17 + "devDependencies": { 18 + "@prettier/plugin-oxc": "^0.0.5", 19 + "@tsconfig/node24": "^24.0.3", 20 + "@types/node": "^24.10.1", 21 + "@vitejs/plugin-vue": "^6.0.2", 22 + "@vue/eslint-config-prettier": "^10.2.0", 23 + "@vue/eslint-config-typescript": "^14.6.0", 24 + "@vue/tsconfig": "^0.8.1", 25 + "eslint": "^9.39.1", 26 + "eslint-plugin-oxlint": "~1.29.0", 27 + "eslint-plugin-vue": "~10.5.1", 28 + "jiti": "^2.6.1", 29 + "npm-run-all2": "^8.0.4", 30 + "oxlint": "~1.29.0", 31 + "prettier": "3.6.2", 32 + "sass-embedded": "^1.97.0", 33 + "typescript": "~5.9.0", 34 + "vite": "npm:rolldown-vite@latest", 35 + "vite-plugin-vue-devtools": "^8.0.5", 36 + "vue-tsc": "^3.1.5", 37 + }, 38 + }, 39 + }, 40 + "packages": { 41 + "@atcute/atproto": ["@atcute/atproto@3.1.9", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w=="], 42 + 43 + "@atcute/bluesky": ["@atcute/bluesky@3.2.14", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.5" } }, "sha512-XlVuF55AYIyplmKvlGLlj+cUvk9ggxNRPczkTPIY991xJ4qDxDHpBJ39ekAV4dWcuBoRo2o9JynzpafPu2ljDA=="], 44 + 45 + "@atcute/client": ["@atcute/client@4.1.1", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.5" } }, "sha512-FROCbTTCeL5u4tO/n72jDEKyKqjdlXMB56Ehve3W/gnnLGCYWvN42sS7tvL1Mgu6sbO3yZwsXKDrmM2No4XpjA=="], 46 + 47 + "@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="], 48 + 49 + "@atcute/identity-resolver": ["@atcute/identity-resolver@1.2.0", "", { "dependencies": { "@atcute/lexicons": "^1.2.5", "@atcute/util-fetch": "^1.0.4", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-5UbSJfdV3JIkF8ksXz7g4nKBWasf2wROvzM66cfvTIWydWFO6/oS1KZd+zo9Eokje5Scf5+jsY9ZfgVARLepXg=="], 50 + 51 + "@atcute/lexicons": ["@atcute/lexicons@1.2.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "esm-env": "^1.2.2" } }, "sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q=="], 52 + 53 + "@atcute/multibase": ["@atcute/multibase@1.1.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.5" } }, "sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg=="], 54 + 55 + "@atcute/oauth-browser-client": ["@atcute/oauth-browser-client@2.0.3", "", { "dependencies": { "@atcute/client": "^4.1.1", "@atcute/identity-resolver": "^1.2.0", "@atcute/lexicons": "^1.2.5", "@atcute/multibase": "^1.1.6", "@atcute/uint8array": "^1.0.6", "nanoid": "^5.1.6" } }, "sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ=="], 56 + 57 + "@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 58 + 59 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.4", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg=="], 60 + 61 + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], 62 + 63 + "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], 64 + 65 + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], 66 + 67 + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], 68 + 69 + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], 70 + 71 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], 72 + 73 + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], 74 + 75 + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], 76 + 77 + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], 78 + 79 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], 80 + 81 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], 82 + 83 + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], 84 + 85 + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], 86 + 87 + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], 88 + 89 + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], 90 + 91 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], 92 + 93 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], 94 + 95 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], 96 + 97 + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], 98 + 99 + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], 100 + 101 + "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.28.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-decorators": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg=="], 102 + 103 + "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A=="], 104 + 105 + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="], 106 + 107 + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], 108 + 109 + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="], 110 + 111 + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="], 112 + 113 + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="], 114 + 115 + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], 116 + 117 + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], 118 + 119 + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], 120 + 121 + "@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="], 122 + 123 + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.10.2", "", {}, "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A=="], 124 + 125 + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], 126 + 127 + "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], 128 + 129 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], 130 + 131 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], 132 + 133 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 134 + 135 + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], 136 + 137 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 138 + 139 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 140 + 141 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], 142 + 143 + "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], 144 + 145 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 146 + 147 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 148 + 149 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 150 + 151 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 152 + 153 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 154 + 155 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 156 + 157 + "@iconify-prerendered/vue-material-symbols": ["@iconify-prerendered/vue-material-symbols@0.28.1755063979", "", { "peerDependencies": { "vue": "^3.0.0" } }, "sha512-twv15c6sQPhr06gAJxPBqdPgGAbaFamMZMECqdzBPePna9mJ6ISV8rBX4bKwan4h3EiSHei/pSu+jOt8G5xVLA=="], 158 + 159 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 160 + 161 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], 162 + 163 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 164 + 165 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 166 + 167 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 168 + 169 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="], 170 + 171 + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 172 + 173 + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 174 + 175 + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 176 + 177 + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.97.0", "", { "os": "android", "cpu": "arm64" }, "sha512-oLCGuX+1zqTIUjTfCxiZO/Ad4p4wo2MksBSpjdgOC7htyfIg/Se9PK2xU2jzSXlIyzBivwK6AJFqJpcbzJlmsQ=="], 178 + 179 + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.97.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Rg7Yy0ICS4HiF+/ZcmjB7h67YOw23Iw06ETHP+0UHQkNuecFew9aDycGG62ohCb1/+QC5uVTW9naR4F8L3FndQ=="], 180 + 181 + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.97.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kr2rgG7yEnv6ivreQtwKAetGeovfWMxsWzTPlM4BMkhI6jsj10BFN+tP5kUHrES66e7eaoFs0SNepHulCpofdw=="], 182 + 183 + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.97.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kAWTFHVR3KLcYQ7oEpRQV+WtEAPWZODQ/FsIVGVNAjzIfm9myuiLh7Kys8Vh3QwATPCuPg1w7FGexIm/A1a1lQ=="], 184 + 185 + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.97.0", "", { "os": "linux", "cpu": "arm" }, "sha512-w4wYc5KRO6Mdxq9wXh6fAMuxB1LX7btj74+fTZG7/eP7ZiCTsxIM0GR4l7xQjRJOFd9rzlu7ZPq3LM7e9wmPTg=="], 186 + 187 + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.97.0", "", { "os": "linux", "cpu": "arm" }, "sha512-DY+3aV2k9YyCRQ5/Zw83cG0xXvgnA6d31JSGfWkOAq9Aa22GeBE/NOzqqMw72HcxRKvYcJsCVpBwQaTICuBGIQ=="], 188 + 189 + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.97.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-4B/H4CSc8LZSBTzQHMHQbbZww8B1qaQO+1iBxeKYo1LBD5ZAUZwgYCyM1VUPgqEfUY358a1/Nhn4RIwAbnEFWw=="], 190 + 191 + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.97.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Wg8ZPEXR3HHDlzvxqFH9XVc6xfnXaEjMmAuJ9priQmMin42O4B5TwvLmBNlW5Is30faKopGXiiH/Gjmcw/x4xg=="], 192 + 193 + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.97.0", "", { "os": "linux", "cpu": "none" }, "sha512-OJNHq6KGPdOh+YVk67T3MfRzLIy9MDMZCzH1f+xgh+kKPWzC4RqlqDNuoyqYiIxjO6kAVZZUQYvx4XVSKluJxw=="], 194 + 195 + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.97.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-yZV1kKNzewd/lwWYBw6IRy7ckbduQsUt5LisM8NXt8T0Dg+jhkyy4y7M6X57/KyvT//vHCuRvpnwTr9lk1M9IA=="], 196 + 197 + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.97.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Ck7cJMsZX19B0dvsl1v9a8VLeL9kEfUc0zMBjkgYmJfhVuINHcnZlQs8E5zTfD+dpP1wYzUhwgqv3o6hl9QaXA=="], 198 + 199 + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.97.0", "", { "os": "linux", "cpu": "x64" }, "sha512-COlEtnuyWfVjvylxhxoSd2HkAI85flvrQu3vGtt4Bm3+ZVdteFCNQskk3q8XfD0Cs+FdtnvDMbhApHyFKaEfsQ=="], 200 + 201 + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.97.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.7" }, "cpu": "none" }, "sha512-5Rt1uEe1VTw6aUluz8/nBNUbyCVGzwMJbXvPv+b4So+mFlkL+X2cTHb9LH8hcBgJ2TDITLT32J2TcV8Q8EPaKw=="], 202 + 203 + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.97.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-e2HDWO997STh7gADYJcjrZ+Fh5cSF8fwT6rRchNoV/hSwbJSC6ZpYFFFQEw2qZ2qyMeTmqQ6QVf7T9oKV18UXg=="], 204 + 205 + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.97.0", "", { "os": "win32", "cpu": "x64" }, "sha512-DQ92RUXw67ynu6fUzlFN/gr/rN3nxEQ35AC3EJYAgNKy/GFFJbNKGwFxGnmooje29XhBwibaRdxDs1OIgZBHvQ=="], 206 + 207 + "@oxc-project/runtime": ["@oxc-project/runtime@0.101.0", "", {}, "sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ=="], 208 + 209 + "@oxc-project/types": ["@oxc-project/types@0.97.0", "", {}, "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ=="], 210 + 211 + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.29.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XYsieDAI0kXJyvayHnmOW1qVydqklRRVT4O5eZmO/rdNCku5CoXsZvBvkPc3U8/9V1mRuen1sxbM9T5JsZqhdA=="], 212 + 213 + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.29.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-s+Ch5/4zDJ6wsOk95xY3BS5mtE2JzHLz7gVZ9OWA9EvhVO84wz2YbDp2JaA314yyqhlX5SAkZ6fj3BRMIcQIqg=="], 214 + 215 + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.29.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qLCgdUkDBG8muK1o3mPgf31rvCPzj1Xff9DHlJjfv+B0ee/hJ2LAoK8EIsQedfQuuiAccOe9GG65BivGCTgKOg=="], 216 + 217 + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.29.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qe62yb1fyW51wo1VBpx9AJJ1Ih1T8NYDeR9AmpNGkrmKN8u3pPbcGXM4mCrOwpwJUG9M/oFvCIlIz2RhawHlkA=="], 218 + 219 + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.29.0", "", { "os": "linux", "cpu": "x64" }, "sha512-4x7p2iVoSE2aT9qI1JOLxUAv3UuzMYGBYWBA4ZF8ln99AdUo1eo0snFacPNd6I/ZZNcv5TegXC+0EUhp5MfYBw=="], 220 + 221 + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.29.0", "", { "os": "linux", "cpu": "x64" }, "sha512-BdH5gdRpaYpyZn2Zm+MCS4b1YmXNe7QyQhw0fawuou+N1LrdAyELgvqI5xXZ1MXCgWDOa6WJaoE6VOPaDc29GA=="], 222 + 223 + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.29.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-y+j9ZDrnMxvRTNIstZKFY7gJD07nT++c4cGmub1ENvhoHVToiQAAZQUOLDhXXRzCrFoG/cFJXJf72uowHZPbcg=="], 224 + 225 + "@oxlint/win32-x64": ["@oxlint/win32-x64@1.29.0", "", { "os": "win32", "cpu": "x64" }, "sha512-F1iRtq8VT96lT8hqOubLyV0GxgIK/XdXk2kFLXdCspiI2ngXeNmTTvmPxrj+WFL6fpJPgv7VKWRb/zEHJnNOrg=="], 226 + 227 + "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], 228 + 229 + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="], 230 + 231 + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="], 232 + 233 + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="], 234 + 235 + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="], 236 + 237 + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="], 238 + 239 + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="], 240 + 241 + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="], 242 + 243 + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="], 244 + 245 + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="], 246 + 247 + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="], 248 + 249 + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="], 250 + 251 + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="], 252 + 253 + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="], 254 + 255 + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], 256 + 257 + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], 258 + 259 + "@prettier/plugin-oxc": ["@prettier/plugin-oxc@0.0.5", "", { "dependencies": { "oxc-parser": "0.97.0" } }, "sha512-A0uIVkwFrEFQJCU/Wpga6pS5t8UQDdVGB+5e7pVMtlPRw69KDmlozcAoLggwdp3FoVpzNGhngMmgfiE8KLs+BA=="], 260 + 261 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.53", "", { "os": "android", "cpu": "arm64" }, "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ=="], 262 + 263 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.53", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg=="], 264 + 265 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.53", "", { "os": "darwin", "cpu": "x64" }, "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ=="], 266 + 267 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.53", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w=="], 268 + 269 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53", "", { "os": "linux", "cpu": "arm" }, "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ=="], 270 + 271 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53", "", { "os": "linux", "cpu": "arm64" }, "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg=="], 272 + 273 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.53", "", { "os": "linux", "cpu": "arm64" }, "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA=="], 274 + 275 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.53", "", { "os": "linux", "cpu": "x64" }, "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA=="], 276 + 277 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.53", "", { "os": "linux", "cpu": "x64" }, "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw=="], 278 + 279 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.53", "", { "os": "none", "cpu": "arm64" }, "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A=="], 280 + 281 + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.53", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.0" }, "cpu": "none" }, "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg=="], 282 + 283 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53", "", { "os": "win32", "cpu": "arm64" }, "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw=="], 284 + 285 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.53", "", { "os": "win32", "cpu": "x64" }, "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA=="], 286 + 287 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="], 288 + 289 + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 290 + 291 + "@tsconfig/node24": ["@tsconfig/node24@24.0.3", "", {}, "sha512-vcERKtKQKHgzt/vfS3Gjasd8SUI2a0WZXpgJURdJsMySpS5+ctgbPfuLj2z/W+w4lAfTWxoN4upKfu2WzIRYnw=="], 292 + 293 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 294 + 295 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 296 + 297 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 298 + 299 + "@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="], 300 + 301 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.50.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/type-utils": "8.50.0", "@typescript-eslint/utils": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.50.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg=="], 302 + 303 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.50.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q=="], 304 + 305 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.50.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.50.0", "@typescript-eslint/types": "^8.50.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ=="], 306 + 307 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0" } }, "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A=="], 308 + 309 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.50.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w=="], 310 + 311 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/utils": "8.50.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw=="], 312 + 313 + "@typescript-eslint/types": ["@typescript-eslint/types@8.50.0", "", {}, "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w=="], 314 + 315 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.50.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.50.0", "@typescript-eslint/tsconfig-utils": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ=="], 316 + 317 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.50.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg=="], 318 + 319 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.50.0", "", { "dependencies": { "@typescript-eslint/types": "8.50.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q=="], 320 + 321 + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.53" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w=="], 322 + 323 + "@volar/language-core": ["@volar/language-core@2.4.26", "", { "dependencies": { "@volar/source-map": "2.4.26" } }, "sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A=="], 324 + 325 + "@volar/source-map": ["@volar/source-map@2.4.26", "", {}, "sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw=="], 326 + 327 + "@volar/typescript": ["@volar/typescript@2.4.26", "", { "dependencies": { "@volar/language-core": "2.4.26", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA=="], 328 + 329 + "@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.5.0", "", {}, "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA=="], 330 + 331 + "@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@1.5.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.2", "@vue/babel-helper-vue-transform-on": "1.5.0", "@vue/babel-plugin-resolve-type": "1.5.0", "@vue/shared": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" }, "optionalPeers": ["@babel/core"] }, "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw=="], 332 + 333 + "@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.5.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/parser": "^7.28.0", "@vue/compiler-sfc": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w=="], 334 + 335 + "@vue/compiler-core": ["@vue/compiler-core@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.26", "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w=="], 336 + 337 + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.26", "", { "dependencies": { "@vue/compiler-core": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A=="], 338 + 339 + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.26", "@vue/compiler-dom": "3.5.26", "@vue/compiler-ssr": "3.5.26", "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA=="], 340 + 341 + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw=="], 342 + 343 + "@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="], 344 + 345 + "@vue/devtools-core": ["@vue/devtools-core@8.0.5", "", { "dependencies": { "@vue/devtools-kit": "^8.0.5", "@vue/devtools-shared": "^8.0.5", "mitt": "^3.0.1", "nanoid": "^5.1.5", "pathe": "^2.0.3", "vite-hot-client": "^2.1.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ=="], 346 + 347 + "@vue/devtools-kit": ["@vue/devtools-kit@8.0.5", "", { "dependencies": { "@vue/devtools-shared": "^8.0.5", "birpc": "^2.6.1", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^2.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg=="], 348 + 349 + "@vue/devtools-shared": ["@vue/devtools-shared@8.0.5", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg=="], 350 + 351 + "@vue/eslint-config-prettier": ["@vue/eslint-config-prettier@10.2.0", "", { "dependencies": { "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2" }, "peerDependencies": { "eslint": ">= 8.21.0", "prettier": ">= 3.0.0" } }, "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw=="], 352 + 353 + "@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.6.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.35.1", "fast-glob": "^3.3.3", "typescript-eslint": "^8.35.1", "vue-eslint-parser": "^10.2.0" }, "peerDependencies": { "eslint": "^9.10.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ=="], 354 + 355 + "@vue/language-core": ["@vue/language-core@3.1.8", "", { "dependencies": { "@volar/language-core": "2.4.26", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.2" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw=="], 356 + 357 + "@vue/reactivity": ["@vue/reactivity@3.5.26", "", { "dependencies": { "@vue/shared": "3.5.26" } }, "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ=="], 358 + 359 + "@vue/runtime-core": ["@vue/runtime-core@3.5.26", "", { "dependencies": { "@vue/reactivity": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q=="], 360 + 361 + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.26", "", { "dependencies": { "@vue/reactivity": "3.5.26", "@vue/runtime-core": "3.5.26", "@vue/shared": "3.5.26", "csstype": "^3.2.3" } }, "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ=="], 362 + 363 + "@vue/server-renderer": ["@vue/server-renderer@3.5.26", "", { "dependencies": { "@vue/compiler-ssr": "3.5.26", "@vue/shared": "3.5.26" }, "peerDependencies": { "vue": "3.5.26" } }, "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA=="], 364 + 365 + "@vue/shared": ["@vue/shared@3.5.26", "", {}, "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="], 366 + 367 + "@vue/tsconfig": ["@vue/tsconfig@0.8.1", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g=="], 368 + 369 + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 370 + 371 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 372 + 373 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 374 + 375 + "alien-signals": ["alien-signals@3.1.1", "", {}, "sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA=="], 376 + 377 + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], 378 + 379 + "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], 380 + 381 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 382 + 383 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 384 + 385 + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA=="], 386 + 387 + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], 388 + 389 + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], 390 + 391 + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 392 + 393 + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 394 + 395 + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], 396 + 397 + "buffer-builder": ["buffer-builder@0.2.0", "", {}, "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg=="], 398 + 399 + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], 400 + 401 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 402 + 403 + "caniuse-lite": ["caniuse-lite@1.0.30001760", "", {}, "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw=="], 404 + 405 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 406 + 407 + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], 408 + 409 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 410 + 411 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 412 + 413 + "colorjs.io": ["colorjs.io@0.5.2", "", {}, "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw=="], 414 + 415 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 416 + 417 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 418 + 419 + "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], 420 + 421 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 422 + 423 + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], 424 + 425 + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], 426 + 427 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 428 + 429 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 430 + 431 + "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], 432 + 433 + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], 434 + 435 + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], 436 + 437 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 438 + 439 + "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], 440 + 441 + "entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="], 442 + 443 + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], 444 + 445 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 446 + 447 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 448 + 449 + "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], 450 + 451 + "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], 452 + 453 + "eslint-plugin-oxlint": ["eslint-plugin-oxlint@1.29.0", "", { "dependencies": { "jsonc-parser": "^3.3.1" } }, "sha512-VmaZ1I0lXJVJokOpnV8F7e339hcFPln5EWY8KGCdTkBnrkRmAeH25GRO6F37lZEDO9e+px5xjqbkq9g3lejBdg=="], 454 + 455 + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.4", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg=="], 456 + 457 + "eslint-plugin-vue": ["eslint-plugin-vue@10.5.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" }, "optionalPeers": ["@stylistic/eslint-plugin", "@typescript-eslint/parser"] }, "sha512-SbR9ZBUFKgvWAbq3RrdCtWaW0IKm6wwUiApxf3BVTNfqUIo4IQQmreMg2iHFJJ6C/0wss3LXURBJ1OwS/MhFcQ=="], 458 + 459 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 460 + 461 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 462 + 463 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 464 + 465 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 466 + 467 + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 468 + 469 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 470 + 471 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 472 + 473 + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 474 + 475 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 476 + 477 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 478 + 479 + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], 480 + 481 + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 482 + 483 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 484 + 485 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 486 + 487 + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], 488 + 489 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 490 + 491 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 492 + 493 + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 494 + 495 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 496 + 497 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 498 + 499 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 500 + 501 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 502 + 503 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 504 + 505 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 506 + 507 + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 508 + 509 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 510 + 511 + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], 512 + 513 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 514 + 515 + "immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="], 516 + 517 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 518 + 519 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 520 + 521 + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], 522 + 523 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 524 + 525 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 526 + 527 + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], 528 + 529 + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 530 + 531 + "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], 532 + 533 + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], 534 + 535 + "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], 536 + 537 + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], 538 + 539 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 540 + 541 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 542 + 543 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 544 + 545 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 546 + 547 + "json-parse-even-better-errors": ["json-parse-even-better-errors@4.0.0", "", {}, "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA=="], 548 + 549 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 550 + 551 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 552 + 553 + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 554 + 555 + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], 556 + 557 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 558 + 559 + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], 560 + 561 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 562 + 563 + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], 564 + 565 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], 566 + 567 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], 568 + 569 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], 570 + 571 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], 572 + 573 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], 574 + 575 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], 576 + 577 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], 578 + 579 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], 580 + 581 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], 582 + 583 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], 584 + 585 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], 586 + 587 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 588 + 589 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 590 + 591 + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 592 + 593 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 594 + 595 + "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], 596 + 597 + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 598 + 599 + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 600 + 601 + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 602 + 603 + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], 604 + 605 + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], 606 + 607 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 608 + 609 + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], 610 + 611 + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], 612 + 613 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 614 + 615 + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], 616 + 617 + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], 618 + 619 + "npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="], 620 + 621 + "npm-run-all2": ["npm-run-all2@8.0.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", "picomatch": "^4.0.2", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", "which": "^5.0.0" }, "bin": { "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js", "npm-run-all": "bin/npm-run-all/index.js", "npm-run-all2": "bin/npm-run-all/index.js" } }, "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA=="], 622 + 623 + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], 624 + 625 + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], 626 + 627 + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], 628 + 629 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 630 + 631 + "oxc-parser": ["oxc-parser@0.97.0", "", { "dependencies": { "@oxc-project/types": "^0.97.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm64": "0.97.0", "@oxc-parser/binding-darwin-arm64": "0.97.0", "@oxc-parser/binding-darwin-x64": "0.97.0", "@oxc-parser/binding-freebsd-x64": "0.97.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.97.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.97.0", "@oxc-parser/binding-linux-arm64-gnu": "0.97.0", "@oxc-parser/binding-linux-arm64-musl": "0.97.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.97.0", "@oxc-parser/binding-linux-s390x-gnu": "0.97.0", "@oxc-parser/binding-linux-x64-gnu": "0.97.0", "@oxc-parser/binding-linux-x64-musl": "0.97.0", "@oxc-parser/binding-wasm32-wasi": "0.97.0", "@oxc-parser/binding-win32-arm64-msvc": "0.97.0", "@oxc-parser/binding-win32-x64-msvc": "0.97.0" } }, "sha512-gxUfidyxJY97BJ+JEN/PxiIxIU1Y1FAPyMTncgNymgd/Cb+TYprsXZqjVnVCmTUlIBoA1XVjbfP0+Iz+uAt7Ow=="], 632 + 633 + "oxlint": ["oxlint@1.29.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.29.0", "@oxlint/darwin-x64": "1.29.0", "@oxlint/linux-arm64-gnu": "1.29.0", "@oxlint/linux-arm64-musl": "1.29.0", "@oxlint/linux-x64-gnu": "1.29.0", "@oxlint/linux-x64-musl": "1.29.0", "@oxlint/win32-arm64": "1.29.0", "@oxlint/win32-x64": "1.29.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.7.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-YqUVUhTYDqazV2qu3QSQn/H4Z1OP+fTnedgZWDk1/lDZxGfR0b1MqRVaEm3rRjBMLHP0zXlriIWUx+DD6UMaPA=="], 634 + 635 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 636 + 637 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 638 + 639 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 640 + 641 + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], 642 + 643 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 644 + 645 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 646 + 647 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 648 + 649 + "perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="], 650 + 651 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 652 + 653 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 654 + 655 + "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], 656 + 657 + "pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="], 658 + 659 + "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=="], 660 + 661 + "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], 662 + 663 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 664 + 665 + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], 666 + 667 + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="], 668 + 669 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 670 + 671 + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 672 + 673 + "read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="], 674 + 675 + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], 676 + 677 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 678 + 679 + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 680 + 681 + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], 682 + 683 + "rolldown": ["rolldown@1.0.0-beta.53", "", { "dependencies": { "@oxc-project/types": "=0.101.0", "@rolldown/pluginutils": "1.0.0-beta.53" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-x64": "1.0.0-beta.53", "@rolldown/binding-freebsd-x64": "1.0.0-beta.53", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw=="], 684 + 685 + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], 686 + 687 + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 688 + 689 + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], 690 + 691 + "sass": ["sass@1.97.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ=="], 692 + 693 + "sass-embedded": ["sass-embedded@1.97.0", "", { "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-all-unknown": "1.97.0", "sass-embedded-android-arm": "1.97.0", "sass-embedded-android-arm64": "1.97.0", "sass-embedded-android-riscv64": "1.97.0", "sass-embedded-android-x64": "1.97.0", "sass-embedded-darwin-arm64": "1.97.0", "sass-embedded-darwin-x64": "1.97.0", "sass-embedded-linux-arm": "1.97.0", "sass-embedded-linux-arm64": "1.97.0", "sass-embedded-linux-musl-arm": "1.97.0", "sass-embedded-linux-musl-arm64": "1.97.0", "sass-embedded-linux-musl-riscv64": "1.97.0", "sass-embedded-linux-musl-x64": "1.97.0", "sass-embedded-linux-riscv64": "1.97.0", "sass-embedded-linux-x64": "1.97.0", "sass-embedded-unknown-all": "1.97.0", "sass-embedded-win32-arm64": "1.97.0", "sass-embedded-win32-x64": "1.97.0" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-Unwu0MtlAt9hQGHutB2NJhwhPcxiJX99AI7PSz7W4lkikQg9S/HYFtgxtIjpTB4DW7sOYX2xnxvtU/nep9HXTA=="], 694 + 695 + "sass-embedded-all-unknown": ["sass-embedded-all-unknown@1.97.0", "", { "dependencies": { "sass": "1.97.0" }, "cpu": [ "!arm", "!x64", "!arm64", ] }, "sha512-9F6MyQcwp3YiuGMk5bC7g9jL+D1KkW/ONQgrkoTQ7ALcmoPKmsauZg5WgRhLYW9UhpnGTgANrWrZdiREAR1YkA=="], 696 + 697 + "sass-embedded-android-arm": ["sass-embedded-android-arm@1.97.0", "", { "os": "android", "cpu": "arm" }, "sha512-VLxeVR5FMwSZoOliBY8Qy2trZCWYz3w4ILf0QZ68eep3mIQjtykY3BSKC2R/w9DkPQDNJXdgbgnxeOubC8k5xw=="], 698 + 699 + "sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.97.0", "", { "os": "android", "cpu": "arm64" }, "sha512-uDG/0DS6A+KRiOYUV1UNHBq67DHvO+/54Ja+dg8S5fl5uvPwZGHpJFheemA9R6vvddwyjGmzVacvCQxdmECcfQ=="], 700 + 701 + "sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.97.0", "", { "os": "android", "cpu": "none" }, "sha512-yrwsyPR08CXW5Ggr0kI1jTUcKkBOtjODbDj11nRrBwyrXRqhf1obqfchQxTW0HlYT8VZmZGfnHvPNNDwOSdfZg=="], 702 + 703 + "sass-embedded-android-x64": ["sass-embedded-android-x64@1.97.0", "", { "os": "android", "cpu": "x64" }, "sha512-a1QW1pFykLCtV8J3AZ+wtrwOx0ORZsW4orF6fOrBYL2sLhlzhB3iK+QzWezFvH5+FMgLQBC4xgYYk4NV9WCO9g=="], 704 + 705 + "sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.97.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5XV42FEqhQEGFQ/w8HUk///k0XMHLyBt1j2alxTr9ZI77HqiAIl6kVZp0kxJ++gt/y3E6hKoMLngHHC6zIBR5A=="], 706 + 707 + "sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.97.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Kc0aKFfKPd/kz8mSGtRKTEN7FKnqs30iZf6APb0ZHMuvMVfOfdD+fZ/85htT+j5k2F+UUSFBpbx04W0gZW020A=="], 708 + 709 + "sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.97.0", "", { "os": "linux", "cpu": "arm" }, "sha512-pwM5A1+w3l1T/FXwJNqZD0WukCENeRkgxPSpZmsO4/QNLdTpGCz16D5spYPQ7f7GZo9aNaHt1EaDLHCjlEA8LQ=="], 710 + 711 + "sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.97.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ofm9esPA9P0sB6wJPcDhQYjSDfa7RoVKD0IHvFPMrK9OLTKg8lw80/afH49a9URYeYiE4wFP76Fr9t+s7A6E1Q=="], 712 + 713 + "sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.97.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+rsW0OreW4sPtdXDewDESxJLJdxW3B0EL7ICajkRFs3KbeNdgOVnP5DJQ39hquAoZH0AcEEGcd6236ZMMzEbwQ=="], 714 + 715 + "sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.97.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-8VF4nc7oUklhUGGAY0T6Ktd9T9ZFwoOsWje7ocOV57tjbocFp/eeAPqX1v2BpiZtMVURyYwaZsRSAL79DT7oRw=="], 716 + 717 + "sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.97.0", "", { "os": "linux", "cpu": "none" }, "sha512-nlaeeZ5P7tde/c/aMiIl5UduQZPA9ftEyWJxdmWcs3pASFSykslVJR5D4L161EUHzB5z+MxSnbbzcrck0F1slA=="], 718 + 719 + "sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.97.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QB6JLr2p1UuEXhiTXEYNypf+w2x/SCMY17vcnXKM47CeaJ88v2C9fJ9oVne6eZntlCylSow/vZCov0JMhklknA=="], 720 + 721 + "sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.97.0", "", { "os": "linux", "cpu": "none" }, "sha512-m7QaK4M+YhQ6FZWMI9O8g4tqmM4JrvzJl/YC/eEJXpfgwxMeXsDsPVQWFiBdWOuxqMSH8WhFksw/Bg0J+kK6VQ=="], 722 + 723 + "sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.97.0", "", { "os": "linux", "cpu": "x64" }, "sha512-yc7yLWJrAtTBCjEAoNxvE040EGYdsgmaWMSyI9LSIOFlSwrOc4x+W/8IMhLWCygTAgorNPuNlRfPDgkQm1sJmw=="], 724 + 725 + "sass-embedded-unknown-all": ["sass-embedded-unknown-all@1.97.0", "", { "dependencies": { "sass": "1.97.0" }, "os": [ "!linux", "!win32", "!darwin", "!android", ] }, "sha512-dDky3ETKeOo543myScL4sp3pj2cANLNKea5aR6v8ZCpDSCDTRxqv4Sj/goTmkVqnp/HOVF88qB3GHtQ8rFtULQ=="], 726 + 727 + "sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.97.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-JMU2SKIgUJDw4oaKBcVbuobWRU6f2XmFuYqJdkxJhlITAGimwjZ860gttlzjNtZcVI4+p4ovT14HwpsEcIzfnw=="], 728 + 729 + "sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.97.0", "", { "os": "win32", "cpu": "x64" }, "sha512-mKIJGXxEl6OoWEoT4ee5OsBOfExla2ilY5J8tupVwSCxf/i3aOJNLm7ZzRWG9er2K3bC8aovgMisMIVGlBM5hw=="], 730 + 731 + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 732 + 733 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 734 + 735 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 736 + 737 + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], 738 + 739 + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], 740 + 741 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 742 + 743 + "speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="], 744 + 745 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 746 + 747 + "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], 748 + 749 + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], 750 + 751 + "sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="], 752 + 753 + "sync-message-port": ["sync-message-port@1.1.3", "", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="], 754 + 755 + "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], 756 + 757 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 758 + 759 + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 760 + 761 + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], 762 + 763 + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 764 + 765 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 766 + 767 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 768 + 769 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 770 + 771 + "typescript-eslint": ["typescript-eslint@8.50.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.50.0", "@typescript-eslint/parser": "8.50.0", "@typescript-eslint/typescript-estree": "8.50.0", "@typescript-eslint/utils": "8.50.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A=="], 772 + 773 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 774 + 775 + "unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="], 776 + 777 + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], 778 + 779 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 780 + 781 + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 782 + 783 + "varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="], 784 + 785 + "vite": ["rolldown-vite@7.3.0", "", { "dependencies": { "@oxc-project/runtime": "0.101.0", "fdir": "^6.5.0", "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.53", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-5hI5NCJwKBGtzWtdKB3c2fOEpI77Iaa0z4mSzZPU1cJ/OqrGbFafm90edVCd7T9Snz+Sh09TMAv4EQqyVLzuEg=="], 786 + 787 + "vite-dev-rpc": ["vite-dev-rpc@1.1.0", "", { "dependencies": { "birpc": "^2.4.0", "vite-hot-client": "^2.1.0" }, "peerDependencies": { "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" } }, "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A=="], 788 + 789 + "vite-hot-client": ["vite-hot-client@2.1.0", "", { "peerDependencies": { "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ=="], 790 + 791 + "vite-plugin-inspect": ["vite-plugin-inspect@11.3.3", "", { "dependencies": { "ansis": "^4.1.0", "debug": "^4.4.1", "error-stack-parser-es": "^1.0.5", "ohash": "^2.0.11", "open": "^10.2.0", "perfect-debounce": "^2.0.0", "sirv": "^3.0.1", "unplugin-utils": "^0.3.0", "vite-dev-rpc": "^1.1.0" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA=="], 792 + 793 + "vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@8.0.5", "", { "dependencies": { "@vue/devtools-core": "^8.0.5", "@vue/devtools-kit": "^8.0.5", "@vue/devtools-shared": "^8.0.5", "sirv": "^3.0.2", "vite-plugin-inspect": "^11.3.3", "vite-plugin-vue-inspector": "^5.3.2" }, "peerDependencies": { "vite": "^6.0.0 || ^7.0.0-0" } }, "sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A=="], 794 + 795 + "vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.3.2", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" } }, "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q=="], 796 + 797 + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], 798 + 799 + "vue": ["vue@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/compiler-sfc": "3.5.26", "@vue/runtime-dom": "3.5.26", "@vue/server-renderer": "3.5.26", "@vue/shared": "3.5.26" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA=="], 800 + 801 + "vue-eslint-parser": ["vue-eslint-parser@10.2.0", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw=="], 802 + 803 + "vue-tsc": ["vue-tsc@3.1.8", "", { "dependencies": { "@volar/typescript": "2.4.26", "@vue/language-core": "3.1.8" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-deKgwx6exIHeZwF601P1ktZKNF0bepaSN4jBU3AsbldPx9gylUc1JDxYppl82yxgkAgaz0Y0LCLOi+cXe9HMYA=="], 804 + 805 + "which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], 806 + 807 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 808 + 809 + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], 810 + 811 + "xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="], 812 + 813 + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 814 + 815 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 816 + 817 + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 818 + 819 + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 820 + 821 + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 822 + 823 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 824 + 825 + "@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], 826 + 827 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 828 + 829 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 830 + 831 + "@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="], 832 + 833 + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 834 + 835 + "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 836 + 837 + "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 838 + 839 + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 840 + 841 + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 842 + 843 + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 844 + 845 + "rolldown/@oxc-project/types": ["@oxc-project/types@0.101.0", "", {}, "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ=="], 846 + 847 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 848 + 849 + "@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="], 850 + 851 + "@vue/devtools-api/@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], 852 + 853 + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 854 + } 855 + }
+1
env.d.ts
··· 1 + /// <reference types="vite/client" />
+24
eslint.config.ts
··· 1 + import { globalIgnores } from 'eslint/config' 2 + import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' 3 + import pluginVue from 'eslint-plugin-vue' 4 + import pluginOxlint from 'eslint-plugin-oxlint' 5 + import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' 6 + 7 + // To allow more languages other than `ts` in `.vue` files, uncomment the following lines: 8 + // import { configureVueProject } from '@vue/eslint-config-typescript' 9 + // configureVueProject({ scriptLangs: ['ts', 'tsx'] }) 10 + // More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup 11 + 12 + export default defineConfigWithVueTs( 13 + { 14 + name: 'app/files-to-lint', 15 + files: ['**/*.{ts,mts,tsx,vue}'], 16 + }, 17 + 18 + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), 19 + 20 + pluginVue.configs['flat/essential'], 21 + vueTsConfigs.recommended, 22 + ...pluginOxlint.configs['flat/recommended'], 23 + skipFormatting, 24 + )
+13
index.html
··· 1 + <!doctype html> 2 + <html lang=""> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <link rel="icon" href="/scilla.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Scilla</title> 8 + </head> 9 + <body> 10 + <div id="app"></div> 11 + <script type="module" src="/src/main.ts"></script> 12 + </body> 13 + </html>
+52
package.json
··· 1 + { 2 + "name": "scilla", 3 + "version": "0.0.0", 4 + "private": true, 5 + "type": "module", 6 + "engines": { 7 + "node": "^20.19.0 || >=22.12.0" 8 + }, 9 + "scripts": { 10 + "dev": "vite", 11 + "build": "run-p type-check \"build-only {@}\" --", 12 + "preview": "vite preview", 13 + "build-only": "vite build", 14 + "type-check": "vue-tsc --build", 15 + "lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore", 16 + "lint:eslint": "eslint . --fix --cache", 17 + "lint": "run-s lint:*", 18 + "format": "prettier --write --experimental-cli src/" 19 + }, 20 + "dependencies": { 21 + "@atcute/atproto": "^3.1.9", 22 + "@atcute/bluesky": "^3.2.14", 23 + "@atcute/client": "^4.1.1", 24 + "@atcute/identity-resolver": "^1.2.0", 25 + "@atcute/lexicons": "^1.2.5", 26 + "@atcute/oauth-browser-client": "^2.0.3", 27 + "@iconify-prerendered/vue-material-symbols": "^0.28.1755063979", 28 + "pinia": "^3.0.4", 29 + "vue": "^3.5.25" 30 + }, 31 + "devDependencies": { 32 + "@prettier/plugin-oxc": "^0.0.5", 33 + "@tsconfig/node24": "^24.0.3", 34 + "@types/node": "^24.10.1", 35 + "@vitejs/plugin-vue": "^6.0.2", 36 + "@vue/eslint-config-prettier": "^10.2.0", 37 + "@vue/eslint-config-typescript": "^14.6.0", 38 + "@vue/tsconfig": "^0.8.1", 39 + "eslint": "^9.39.1", 40 + "eslint-plugin-oxlint": "~1.29.0", 41 + "eslint-plugin-vue": "~10.5.1", 42 + "jiti": "^2.6.1", 43 + "npm-run-all2": "^8.0.4", 44 + "oxlint": "~1.29.0", 45 + "prettier": "3.6.2", 46 + "sass-embedded": "^1.97.0", 47 + "typescript": "~5.9.0", 48 + "vite": "npm:rolldown-vite@latest", 49 + "vite-plugin-vue-devtools": "^8.0.5", 50 + "vue-tsc": "^3.1.5" 51 + } 52 + }
+13
public/oauth-client-metadata.json
··· 1 + { 2 + "client_id": "http://localhost:5173/oauth-client-metadata.json", 3 + "client_name": "Scilla", 4 + "client_uri": "http://localhost:5173", 5 + "logo_uri": "http://localhost:5173/favicon.ico", 6 + "redirect_uris": ["http://localhost:5173/oauth/callback"], 7 + "scope": "atproto transition:generic", 8 + "grant_types": ["authorization_code", "refresh_token"], 9 + "response_types": ["code"], 10 + "token_endpoint_auth_method": "none", 11 + "application_type": "web", 12 + "dpop_bound_access_tokens": true 13 + }
+3
public/scilla.svg
··· 1 + <svg width="2048" height="2048" viewBox="0 0 2048 2048" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <rect width="2048" height="2048" fill="#301B92"/> 3 + </svg>
+142
src/App.vue
··· 1 + <script setup lang="ts"> 2 + import { onMounted, computed, ref } from 'vue' 3 + import { useNavigationStore } from './stores/navigation' 4 + import { useEnvironmentStore } from './stores/environment' 5 + import { useThemeStore } from './stores/theme' 6 + import { useAuthStore } from './stores/auth' 7 + 8 + import TabStack from '@/components/Navigation/TabStack.vue' 9 + import NavigationBar from '@/components/Navigation/NavigationBar.vue' 10 + import OAuthCallback from '@/views/Auth/OAuthCallback.vue' 11 + 12 + import { stackRoots, type StackRootNames } from './router' 13 + 14 + const nav = useNavigationStore() 15 + const env = useEnvironmentStore() 16 + const theme = useThemeStore() 17 + const auth = useAuthStore() 18 + 19 + const activeTab = computed(() => nav.activeTab) 20 + const tabs: StackRootNames[] = stackRoots.map((p) => p.name) 21 + 22 + const isCallback = ref(window.location.pathname.includes('/oauth/callback')) 23 + auth.init() 24 + const onAuthComplete = () => { 25 + window.history.replaceState(null, '', '/') 26 + 27 + theme.init() 28 + env.init() 29 + auth.init() 30 + isCallback.value = false 31 + } 32 + 33 + onMounted(async () => { 34 + theme.init() 35 + env.init() 36 + 37 + if (!isCallback.value) { 38 + nav.init() 39 + } 40 + }) 41 + </script> 42 + 43 + <template> 44 + <Transition name="app-fade" mode="in-out"> 45 + <OAuthCallback v-if="isCallback" @complete="onAuthComplete" class="view-layer" /> 46 + 47 + <div v-else class="app-shell view-layer"> 48 + <div class="skip-links"> 49 + <a href="#main-content" id="skip-to-content" class="skip-link"> skip to main content </a> 50 + <a href="#navigation-bar" class="skip-link"> skip to navigation </a> 51 + </div> 52 + 53 + <div class="viewport" id="main-content"> 54 + <TabStack 55 + v-for="t in tabs" 56 + :key="t" 57 + :tab="t" 58 + v-show="activeTab === t" 59 + :class="{ active: activeTab === t }" 60 + /> 61 + </div> 62 + <NavigationBar ref="navBar" /> 63 + </div> 64 + </Transition> 65 + </template> 66 + 67 + <style scoped> 68 + .view-layer { 69 + position: absolute; 70 + inset: 0; 71 + width: 100vw; 72 + height: 100vh; 73 + } 74 + 75 + .app-fade-enter-active, 76 + .app-fade-leave-active { 77 + transition: opacity 0.8s ease; 78 + } 79 + 80 + .app-fade-enter-from { 81 + opacity: 0; 82 + transform: scale(0.98); 83 + } 84 + 85 + .app-fade-leave-to { 86 + opacity: 0; 87 + } 88 + 89 + .app-shell { 90 + position: relative; 91 + height: 100vh; 92 + width: 100vw; 93 + overflow: hidden; 94 + background-color: hsla(var(--mantle) / 1); 95 + display: flex; 96 + flex-direction: column; 97 + } 98 + 99 + .viewport { 100 + position: relative; 101 + flex: 1; 102 + overflow: hidden; 103 + z-index: 0; 104 + } 105 + 106 + @media (min-width: 512px) { 107 + .app-shell { 108 + flex-direction: row; 109 + justify-content: center; 110 + } 111 + 112 + .viewport { 113 + flex: 1; 114 + order: 2; 115 + max-width: 768px; 116 + } 117 + } 118 + 119 + .skip-links { 120 + position: absolute; 121 + top: -100px; 122 + left: 0; 123 + z-index: 1000; 124 + } 125 + 126 + .skip-link { 127 + position: absolute; 128 + top: -100px; 129 + left: 8px; 130 + background: hsl(var(--overlay0)); 131 + color: hsl(var(--blue)); 132 + padding: 8px 16px; 133 + text-decoration: none; 134 + border-radius: 4px; 135 + font-weight: 600; 136 + z-index: 11000; 137 + &:focus { 138 + top: 128px; 139 + width: fit-content; 140 + } 141 + } 142 + </style>
+3
src/assets/icons/bluesky.svg
··· 1 + <svg width="568" height="501" viewBox="0 0 568 501" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <path d="M123.121 33.6637C188.241 82.5526 258.281 181.681 284 234.873C309.719 181.681 379.759 82.5526 444.879 33.6637C491.866 -1.61183 568 -28.9064 568 57.9464C568 75.2916 558.055 203.659 552.222 224.501C531.947 296.954 458.067 315.434 392.347 304.249C507.222 323.8 536.444 388.56 473.333 453.32C353.473 576.312 301.061 422.461 287.631 383.039C285.169 375.812 284.017 372.431 284 375.306C283.983 372.431 282.831 375.812 280.369 383.039C266.939 422.461 214.527 576.312 94.6667 453.32C31.5556 388.56 60.7778 323.8 175.653 304.249C109.933 315.434 36.0535 296.954 15.7778 224.501C9.94525 203.659 0 75.2916 0 57.9464C0 -28.9064 76.1345 -1.61183 123.121 33.6637Z" fill="currentColor"/> 3 + </svg>
+3
src/assets/icons/scilla.svg
··· 1 + <svg width="2048" height="2048" viewBox="0 0 2048 2048" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <rect width="2048" height="2048" fill="#301B92"/> 3 + </svg>
+5
src/assets/icons/tangled.svg
··· 1 + <svg version="1.1" id="svg1" width="24.122343" height="23.274094" viewBox="0 0 24.122343 23.274094" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> 2 + <g id="g1" transform="translate(-0.4388285,-0.8629527)"> 3 + <path style="fill:currentColor;fill-opacity:1;stroke-width:0.111;stroke-dasharray:none" d="m 16.348974,24.09935 -0.06485,-0.03766 -0.202005,-0.0106 -0.202008,-0.01048 -0.275736,-0.02601 -0.275734,-0.02602 v -0.02649 -0.02648 l -0.204577,-0.04019 -0.204578,-0.04019 -0.167616,-0.08035 -0.167617,-0.08035 -0.0014,-0.04137 -0.0014,-0.04137 -0.266473,-0.143735 -0.266475,-0.143735 -0.276098,-0.20335 -0.2761,-0.203347 -0.262064,-0.251949 -0.262064,-0.25195 -0.22095,-0.284628 -0.220948,-0.284629 -0.170253,-0.284631 -0.170252,-0.284628 -0.01341,-0.0144 -0.0134,-0.0144 -0.141982,0.161297 -0.14198,0.1613 -0.22313,0.21426 -0.223132,0.214264 -0.186025,0.146053 -0.186023,0.14605 -0.252501,0.163342 -0.252502,0.163342 -0.249014,0.115348 -0.249013,0.115336 0.0053,0.03241 0.0053,0.03241 -0.1716725,0.04599 -0.171669,0.046 -0.3379966,0.101058 -0.3379972,0.101058 -0.1778925,0.04506 -0.1778935,0.04508 -0.3913655,0.02601 -0.3913643,0.02603 -0.3557868,-0.03514 -0.3557863,-0.03514 -0.037426,-0.03029 -0.037427,-0.03029 -0.076924,0.02011 -0.076924,0.02011 -0.050508,-0.05051 -0.050405,-0.05056 L 6.6604532,23.110188 6.451745,23.063961 6.1546135,22.960559 5.8574835,22.857156 5.5319879,22.694039 5.2064938,22.530922 4.8793922,22.302961 4.5522905,22.075005 4.247598,21.786585 3.9429055,21.49817 3.7185335,21.208777 3.4941628,20.919385 3.3669822,20.705914 3.239803,20.492443 3.1335213,20.278969 3.0272397,20.065499 2.9015252,19.7275 2.7758105,19.389504 2.6925225,18.998139 2.6092345,18.606774 2.6096814,17.91299 2.6101284,17.219208 2.6744634,16.90029 2.7387984,16.581374 2.8474286,16.242088 2.9560588,15.9028 3.1137374,15.583492 3.2714148,15.264182 3.3415068,15.150766 3.4115988,15.03735 3.3127798,14.96945 3.2139618,14.90157 3.0360685,14.800239 2.8581753,14.698908 2.5913347,14.503228 2.3244955,14.307547 2.0621238,14.055599 1.7997507,13.803651 1.6111953,13.56878 1.4226411,13.333906 1.2632237,13.087474 1.1038089,12.841042 0.97442,12.575195 0.8450307,12.30935 0.724603,11.971351 0.6041766,11.633356 0.52150365,11.241991 0.4388285,10.850626 0.44091592,10.156842 0.44300333,9.4630594 0.54235911,9.0369608 0.6417149,8.6108622 0.7741173,8.2694368 0.9065196,7.9280115 1.0736303,7.6214262 1.2407515,7.3148397 1.45931,7.0191718 1.6778685,6.7235039 1.9300326,6.4611321 2.1821966,6.1987592 2.4134579,6.0137228 2.6447193,5.8286865 2.8759792,5.6776409 3.1072406,5.526594 3.4282004,5.3713977 3.7491603,5.2162016 3.9263009,5.1508695 4.1034416,5.0855373 4.2813348,4.7481598 4.4592292,4.4107823 4.6718,4.108422 4.8843733,3.8060618 5.198353,3.4805372 5.5123313,3.155014 5.7685095,2.9596425 6.0246877,2.7642722 6.329187,2.5851365 6.6336863,2.406002 6.9497657,2.2751596 7.2658453,2.1443184 7.4756394,2.0772947 7.6854348,2.01027 8.0825241,1.931086 8.4796139,1.851902 l 0.5870477,0.00291 0.5870469,0.00291 0.4447315,0.092455 0.444734,0.092455 0.302419,0.1105495 0.302417,0.1105495 0.329929,0.1646046 0.32993,0.1646033 0.239329,-0.2316919 0.239329,-0.2316919 0.160103,-0.1256767 0.160105,-0.1256767 0.160102,-0.1021909 0.160105,-0.1021899 0.142315,-0.082328 0.142314,-0.082328 0.231262,-0.1090091 0.231259,-0.1090091 0.26684,-0.098743 0.266839,-0.098743 0.320208,-0.073514 0.320209,-0.073527 0.355787,-0.041833 0.355785,-0.041834 0.426942,0.023827 0.426945,0.023828 0.355785,0.071179 0.355788,0.0711791 0.284627,0.09267 0.284629,0.09267 0.28514,0.1310267 0.28514,0.1310255 0.238179,0.1446969 0.238174,0.1446979 0.259413,0.1955332 0.259413,0.1955319 0.290757,0.296774 0.290758,0.2967753 0.151736,0.1941581 0.151734,0.1941594 0.135326,0.2149951 0.135327,0.2149952 0.154755,0.3202073 0.154758,0.3202085 0.09409,0.2677358 0.09409,0.267737 0.06948,0.3319087 0.06948,0.3319099 0.01111,0.00808 0.01111,0.00808 0.444734,0.2173653 0.444734,0.2173665 0.309499,0.2161102 0.309497,0.2161101 0.309694,0.2930023 0.309694,0.2930037 0.18752,0.2348726 0.187524,0.2348727 0.166516,0.2574092 0.166519,0.2574108 0.15273,0.3260252 0.152734,0.3260262 0.08972,0.2668403 0.08971,0.2668391 0.08295,0.3913655 0.08295,0.3913652 -6.21e-4,0.6582049 -6.21e-4,0.658204 -0.06362,0.315725 -0.06362,0.315725 -0.09046,0.289112 -0.09046,0.289112 -0.122759,0.281358 -0.12276,0.281356 -0.146626,0.252323 -0.146629,0.252322 -0.190443,0.258668 -0.190448,0.258671 -0.254911,0.268356 -0.254911,0.268355 -0.286872,0.223127 -0.286874,0.223127 -0.320203,0.187693 -0.320209,0.187693 -0.04347,0.03519 -0.04347,0.03521 0.0564,0.12989 0.0564,0.129892 0.08728,0.213472 0.08728,0.213471 0.189755,0.729363 0.189753,0.729362 0.0652,0.302417 0.0652,0.302419 -0.0018,0.675994 -0.0018,0.675995 -0.0801,0.373573 -0.08009,0.373577 -0.09,0.266839 -0.09,0.26684 -0.190389,0.391364 -0.19039,0.391366 -0.223169,0.320207 -0.223167,0.320209 -0.303585,0.315294 -0.303584,0.315291 -0.284631,0.220665 -0.284629,0.220663 -0.220128,0.132359 -0.220127,0.132358 -0.242395,0.106698 -0.242394,0.106699 -0.08895,0.04734 -0.08895,0.04733 -0.249052,0.07247 -0.24905,0.07247 -0.322042,0.0574 -0.322044,0.0574 -0.282794,-0.003 -0.282795,-0.003 -0.07115,-0.0031 -0.07115,-0.0031 -0.177894,-0.0033 -0.177893,-0.0033 -0.124528,0.02555 -0.124528,0.02555 z m -4.470079,-5.349839 0.214838,-0.01739 0.206601,-0.06782 0.206602,-0.06782 0.244389,-0.117874 0.244393,-0.11786 0.274473,-0.206822 0.27447,-0.20682 0.229308,-0.257201 0.229306,-0.2572 0.219161,-0.28463 0.219159,-0.284629 0.188541,-0.284628 0.188543,-0.28463 0.214594,-0.373574 0.214593,-0.373577 0.133861,-0.312006 0.133865,-0.312007 0.02861,-0.01769 0.02861,-0.01769 0.197275,0.26212 0.197278,0.262119 0.163613,0.150814 0.163614,0.150814 0.201914,0.09276 0.201914,0.09276 0.302417,0.01421 0.302418,0.01421 0.213472,-0.08025 0.213471,-0.08025 0.200606,-0.204641 0.200606,-0.204642 0.09242,-0.278887 0.09241,-0.278888 0.05765,-0.302418 0.05764,-0.302416 L 18.41327,13.768114 18.39502,13.34117 18.31849,12.915185 18.24196,12.4892 18.15595,12.168033 18.06994,11.846867 17.928869,11.444534 17.787801,11.042201 17.621278,10.73296 17.454757,10.423723 17.337388,10.263619 17.220021,10.103516 17.095645,9.9837986 16.971268,9.8640816 16.990048,9.6813736 17.008828,9.4986654 16.947568,9.249616 16.886308,9.0005655 16.752419,8.7159355 16.618521,8.4313217 16.435707,8.2294676 16.252892,8.0276114 16.079629,7.9004245 15.906366,7.773238 l -0.20429,0.1230127 -0.204289,0.1230121 -0.26702,0.059413 -0.267022,0.059413 -0.205761,-0.021508 -0.205766,-0.021508 -0.23495,-0.08844 -0.234953,-0.08844 -0.118429,-0.090334 -0.118428,-0.090333 h -0.03944 -0.03944 L 13.711268,7.8540732 13.655958,7.9706205 13.497227,8.1520709 13.338499,8.3335203 13.168394,8.4419112 12.998289,8.550301 12.777045,8.624223 12.5558,8.698155 H 12.275611 11.995429 L 11.799973,8.6309015 11.604513,8.5636472 11.491311,8.5051061 11.37811,8.446565 11.138172,8.2254579 10.898231,8.0043497 l -0.09565,-0.084618 -0.09565,-0.084613 -0.218822,0.198024 -0.218822,0.1980231 -0.165392,0.078387 -0.1653925,0.078387 -0.177894,0.047948 -0.177892,0.047948 L 9.3635263,8.4842631 9.144328,8.4846889 8.9195029,8.4147138 8.6946778,8.3447386 8.5931214,8.4414036 8.491565,8.5380686 8.3707618,8.7019598 8.2499597,8.8658478 8.0802403,8.9290726 7.9105231,8.9922974 7.7952769,9.0780061 7.6800299,9.1637148 7.5706169,9.2778257 7.4612038,9.3919481 7.1059768,9.9205267 6.7507497,10.449105 l -0.2159851,0.449834 -0.2159839,0.449834 -0.2216572,0.462522 -0.2216559,0.462523 -0.1459343,0.337996 -0.1459342,0.337998 -0.055483,0.220042 -0.055483,0.220041 -0.015885,0.206903 -0.015872,0.206901 0.034307,0.242939 0.034307,0.24294 0.096281,0.196632 0.096281,0.196634 0.143607,0.125222 0.1436071,0.125222 0.1873143,0.08737 0.1873141,0.08737 0.2752084,0.002 0.2752084,0.002 0.2312297,-0.09773 0.231231,-0.09772 0.1067615,-0.07603 0.1067614,-0.07603 0.3679062,-0.29377 0.3679065,-0.293771 0.026804,0.01656 0.026804,0.01656 0.023626,0.466819 0.023626,0.466815 0.088326,0.513195 0.088326,0.513193 0.08897,0.364413 0.08897,0.364411 0.1315362,0.302418 0.1315352,0.302418 0.1051964,0.160105 0.1051954,0.160103 0.1104741,0.11877 0.1104731,0.118769 0.2846284,0.205644 0.2846305,0.205642 0.144448,0.07312 0.144448,0.07312 0.214787,0.05566 0.214787,0.05566 0.245601,0.03075 0.245602,0.03075 0.204577,-0.0125 0.204578,-0.0125 z m 0.686342,-3.497495 -0.11281,-0.06077 -0.106155,-0.134033 -0.106155,-0.134031 -0.04406,-0.18371 -0.04406,-0.183707 0.02417,-0.553937 0.02417,-0.553936 0.03513,-0.426945 0.03513,-0.426942 0.07225,-0.373576 0.07225,-0.373575 0.05417,-0.211338 0.05417,-0.211339 0.0674,-0.132112 0.0674,-0.132112 0.132437,-0.10916 0.132437,-0.109161 0.187436,-0.04195 0.187438,-0.04195 0.170366,0.06469 0.170364,0.06469 0.114312,0.124073 0.114313,0.124086 0.04139,0.18495 0.04139,0.184951 -0.111218,0.459845 -0.111219,0.459844 -0.03383,0.26584 -0.03382,0.265841 -0.03986,0.818307 -0.03986,0.818309 -0.0378,0.15162 -0.03779,0.151621 -0.11089,0.110562 -0.110891,0.110561 -0.114489,0.04913 -0.114489,0.04913 -0.187932,-0.0016 -0.187929,-0.0016 z m -2.8087655,-0.358124 -0.146445,-0.06848 -0.088025,-0.119502 -0.088024,-0.119502 -0.038581,-0.106736 -0.038581,-0.106736 -0.02237,-0.134956 -0.02239,-0.134957 -0.031955,-0.46988 -0.031955,-0.469881 0.036203,-0.444733 0.036203,-0.444731 0.048862,-0.215257 0.048862,-0.215255 0.076082,-0.203349 0.076081,-0.203348 0.0936,-0.111244 0.0936,-0.111245 0.143787,-0.06531 0.1437865,-0.06532 h 0.142315 0.142314 l 0.142314,0.06588 0.142316,0.06588 0.093,0.102325 0.093,0.102325 0.04042,0.120942 0.04042,0.120942 v 0.152479 0.152477 l -0.03347,0.08804 -0.03347,0.08805 -0.05693,0.275653 -0.05693,0.275651 2.11e-4,0.430246 2.12e-4,0.430243 0.04294,0.392646 0.04295,0.392647 -0.09189,0.200702 -0.09189,0.200702 -0.148688,0.0984 -0.148687,0.0984 -0.20136,0.01212 -0.2013595,0.01212 z" id="path4"/> 4 + </g> 5 + </svg>
+172
src/assets/main.css
··· 1 + @font-face { 2 + font-family: "OpenDyslexic"; 3 + src: 4 + url("/fonts/OpenDyslexic-Regular.woff2") format("woff2"), 5 + url("/fonts/OpenDyslexic-Regular.woff") format("woff"); 6 + font-weight: 400; 7 + font-style: normal; 8 + font-display: swap; 9 + } 10 + 11 + *, 12 + *::before, 13 + *::after { 14 + box-sizing: border-box; 15 + margin: 0; 16 + padding: 0; 17 + font-weight: normal; 18 + 19 + --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); 20 + -webkit-tap-highlight-color: transparent; 21 + outline: 2px solid transparent; 22 + outline-offset: 4px; 23 + 24 + transition: 25 + outline-color var(--transition), 26 + outline-offset var(--transition), 27 + color var(--transition), 28 + background-color var(--transition), 29 + box-shadow var(--transition), 30 + outline var(--transition), 31 + border-color var(--transition), 32 + border-radius var(--transition), 33 + font-weight var(--transition), 34 + opacity var(--transition), 35 + transform var(--transition), 36 + backdrop-filter var(--transition), 37 + text-decoration-color var(--transition), 38 + filter var(--transition); 39 + 40 + &:focus-visible { 41 + outline-color: hsl(var(--blue)); 42 + outline-offset: 2px; 43 + border-radius: 2px; 44 + } 45 + } 46 + 47 + :root { 48 + --space-1: 0.25rem; 49 + --space-2: 0.5rem; 50 + --space-3: 0.75rem; 51 + --space-4: 1rem; 52 + --space-6: 1.5rem; 53 + --space-8: 2rem; 54 + --space-12: 3rem; 55 + 56 + --radius-sm: 0.5rem; 57 + --radius-md: 0.75rem; 58 + --radius-lg: 1rem; 59 + --radius-xl: 1.5rem; 60 + --radius-full: 9999px; 61 + 62 + --max-width: 1200px; 63 + --content-width: 800px; 64 + 65 + --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.1); 66 + --ease-out: cubic-bezier(0.215, 0.61, 0.355, 1); 67 + --ease-in-out: cubic-bezier(0.645, 0.045, 0.355, 1); 68 + } 69 + 70 + body { 71 + background-color: hsl(var(--base)); 72 + color: hsl(var(--text)); 73 + font-family: 74 + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", 75 + Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 76 + font-size: 15px; 77 + line-height: 1.5; 78 + text-rendering: optimizeLegibility; 79 + -webkit-font-smoothing: antialiased; 80 + -moz-osx-font-smoothing: grayscale; 81 + overflow: hidden; 82 + } 83 + 84 + ::selection { 85 + background-color: hsla(var(--rosewater) / 0.3); 86 + color: hsl(var(--text)); 87 + } 88 + 89 + ::-webkit-scrollbar { 90 + width: 8px; 91 + height: 8px; 92 + } 93 + ::-webkit-scrollbar-track { 94 + background: transparent; 95 + } 96 + ::-webkit-scrollbar-thumb { 97 + background: hsla(var(--surface2) / 0.5); 98 + border-radius: 10px; 99 + } 100 + ::-webkit-scrollbar-thumb:hover { 101 + background: hsla(var(--overlay0) / 0.8); 102 + } 103 + 104 + h1, 105 + .text-h1 { 106 + font-size: 2rem; 107 + font-weight: 800; 108 + letter-spacing: -0.025em; 109 + line-height: 1.1; 110 + color: hsl(var(--text)); 111 + } 112 + h2, 113 + .text-h2 { 114 + font-size: 1.5rem; 115 + font-weight: 700; 116 + letter-spacing: -0.025em; 117 + line-height: 1.2; 118 + color: hsl(var(--text)); 119 + } 120 + h3, 121 + .text-h3 { 122 + font-size: 1.25rem; 123 + font-weight: 600; 124 + letter-spacing: -0.015em; 125 + line-height: 1.3; 126 + color: hsl(var(--text)); 127 + } 128 + p .text-body { 129 + font-size: 1rem; 130 + line-height: 1.6; 131 + color: hsl(var(--text)); 132 + } 133 + .text-sm { 134 + font-size: 0.875rem; 135 + color: hsl(var(--subtext0)); 136 + line-height: 1.4; 137 + } 138 + .text-caption { 139 + font-size: 0.75rem; 140 + color: hsl(var(--subtext0)); 141 + text-transform: uppercase; 142 + letter-spacing: 0.06em; 143 + font-weight: 700; 144 + } 145 + 146 + .flex { 147 + display: flex; 148 + } 149 + .flex-col { 150 + flex-direction: column; 151 + } 152 + .items-center { 153 + align-items: center; 154 + } 155 + .justify-between { 156 + justify-content: space-between; 157 + } 158 + .justify-center { 159 + justify-content: center; 160 + } 161 + .gap-2 { 162 + gap: var(--space-2); 163 + } 164 + .gap-4 { 165 + gap: var(--space-4); 166 + } 167 + .w-full { 168 + width: 100%; 169 + } 170 + .mb-4 { 171 + margin-bottom: var(--space-4); 172 + }
+195
src/components/Navigation/AppBar.vue
··· 1 + <script setup lang="ts"> 2 + import { computed, ref, onMounted } from 'vue' 3 + import { IconArrowBackRounded } from '@iconify-prerendered/vue-material-symbols' 4 + import { useNavigationStore, type StackEntry } from '@/stores/navigation' 5 + 6 + const appBarElement = ref<HTMLElement>() 7 + const overlayElement = ref<HTMLElement>() 8 + defineExpose({ $el: appBarElement, $overlay: overlayElement }) 9 + 10 + const nav = useNavigationStore() 11 + const props = defineProps<{ 12 + left?: number 13 + root?: boolean 14 + title?: string 15 + }>() 16 + 17 + const contextTab = ref<string | null>(null) 18 + const contextEntryId = ref<string | null>(null) 19 + 20 + onMounted(() => { 21 + if (!appBarElement.value) return 22 + 23 + const tabEl = appBarElement.value.closest('[data-tab]') 24 + if (tabEl instanceof HTMLElement && tabEl.dataset.tab) { 25 + contextTab.value = tabEl.dataset.tab 26 + } 27 + 28 + const entryEl = appBarElement.value.closest('[data-entry-id]') 29 + if (entryEl instanceof HTMLElement && entryEl.dataset.entryId) { 30 + contextEntryId.value = entryEl.dataset.entryId 31 + } 32 + }) 33 + 34 + const activeTab = computed(() => nav.activeTab) 35 + const stack = computed(() => { 36 + const tab = contextTab.value ?? activeTab.value 37 + return nav.stacks[tab] 38 + }) 39 + 40 + const canGoBack = computed(() => { 41 + if (props.root) return false 42 + 43 + const s = stack.value 44 + if (!s || s.length <= 1) return false 45 + 46 + if (contextEntryId.value) { 47 + const index = s.findIndex((e: StackEntry) => e.id === contextEntryId.value) 48 + return index > 0 49 + } 50 + 51 + return true 52 + }) 53 + 54 + function goBack() { 55 + if (canGoBack.value) nav.popStack() 56 + } 57 + 58 + const displayTitle = computed(() => { 59 + if (props.title) return props.title 60 + 61 + if (contextEntryId.value) { 62 + const entry = stack.value?.find((e: StackEntry) => e.id === contextEntryId.value) 63 + if (entry?.title) return entry.title 64 + } 65 + 66 + const topEntry = stack.value?.[stack.value.length - 1] 67 + return topEntry?.title ?? 'Page' 68 + }) 69 + </script> 70 + 71 + <template> 72 + <div 73 + class="overlay" 74 + ref="overlayElement" 75 + aria-hidden="true" 76 + :style="{ 77 + height: `calc(var(--safe-area-inset-top, 0) + ${appBarElement?.offsetHeight || 0}px)`, 78 + }" 79 + ></div> 80 + <header 81 + ref="appBarElement" 82 + class="topbar" 83 + role="banner" 84 + :style="{ '--left': `${props.left || 0}px` }" 85 + > 86 + <div class="topbar-buffer" aria-hidden="true"></div> 87 + <div class="topbar-content"> 88 + <div class="topbar-left"> 89 + <button 90 + v-if="canGoBack" 91 + @click="goBack" 92 + class="topbar-icon-btn" 93 + aria-label="Go back" 94 + type="button" 95 + > 96 + <IconArrowBackRounded class="icon" aria-hidden="true" /> 97 + </button> 98 + 99 + <h1 class="topbar-title" id="page-title"> 100 + {{ displayTitle }} 101 + </h1> 102 + </div> 103 + <div class="topbar-right" role="toolbar" aria-label="Page actions"> 104 + <slot name="actions" /> 105 + </div> 106 + </div> 107 + </header> 108 + </template> 109 + 110 + <style scoped lang="scss"> 111 + .overlay { 112 + position: absolute; 113 + top: 0; 114 + left: 0; 115 + width: 100%; 116 + background: linear-gradient(to bottom, hsla(var(--base) / 1) 0%, hsla(var(--base) / 0) 100%); 117 + pointer-events: none; 118 + z-index: 99; 119 + } 120 + 121 + .topbar { 122 + background: hsla(var(--base) / 0.8); 123 + backdrop-filter: blur(12px); 124 + border-bottom: 1px solid hsla(var(--surface2) / 0.3); 125 + 126 + position: absolute; 127 + top: 0; 128 + left: 0; 129 + width: 100%; 130 + z-index: 100; 131 + 132 + transition-property: 133 + color, background-color, box-shadow, outline, border-color, border-radius, font-weight, opacity, 134 + backdrop-filter, filter; 135 + 136 + &-buffer { 137 + height: calc(var(--safe-area-inset-top)); 138 + } 139 + 140 + &-content { 141 + display: flex; 142 + align-items: center; 143 + justify-content: space-between; 144 + padding: 0.5rem 1rem; 145 + min-height: 3.5rem; 146 + } 147 + 148 + &-left { 149 + display: flex; 150 + align-items: center; 151 + gap: 0.75rem; 152 + flex: 1; 153 + min-width: 0; 154 + 155 + .topbar-icon-btn { 156 + border: none; 157 + background: transparent; 158 + display: flex; 159 + align-items: center; 160 + justify-content: center; 161 + cursor: pointer; 162 + color: hsl(var(--text)); 163 + border-radius: 50%; 164 + padding: 0.5rem; 165 + 166 + &:hover { 167 + background: hsla(var(--surface1) / 1); 168 + } 169 + &:active { 170 + background: hsla(var(--surface2) / 1); 171 + } 172 + } 173 + 174 + .topbar-title { 175 + font-size: 1.125rem; 176 + font-weight: 700; 177 + color: hsl(var(--text)); 178 + white-space: nowrap; 179 + overflow: hidden; 180 + text-overflow: ellipsis; 181 + } 182 + } 183 + 184 + &-right { 185 + display: flex; 186 + align-items: center; 187 + gap: 0.5rem; 188 + } 189 + } 190 + 191 + .icon { 192 + width: 1.5rem; 193 + height: 1.5rem; 194 + } 195 + </style>
+63
src/components/Navigation/AppLink.vue
··· 1 + <script lang="ts" setup> 2 + import { computed } from 'vue' 3 + import type { PageNames } from '@/router' 4 + import { useNavigationStore } from '@/stores/navigation' 5 + import { getPageByName, compileUrl } from '@/router' 6 + 7 + const props = defineProps<{ 8 + name: PageNames 9 + params?: Record<string, string> 10 + ariaLabel?: string 11 + }>() 12 + 13 + const nav = useNavigationStore() 14 + 15 + const href = computed(() => { 16 + const page = getPageByName(props.name) 17 + if (!page) return '#' 18 + 19 + const { url, remainingProps } = compileUrl(page.path, props.params || {}) 20 + 21 + let path = url 22 + if (Object.keys(remainingProps).length > 0) { 23 + const searchParams = new URLSearchParams() 24 + Object.entries(remainingProps).forEach(([key, value]) => { 25 + if (typeof value === 'object') { 26 + searchParams.set(key, JSON.stringify(value)) 27 + } else { 28 + searchParams.set(key, String(value)) 29 + } 30 + }) 31 + path += `?${searchParams.toString()}` 32 + } 33 + 34 + return path 35 + }) 36 + 37 + const handleClick = (e: MouseEvent) => { 38 + if (e.metaKey || e.ctrlKey) return 39 + e.preventDefault() 40 + nav.push(props.name, { 41 + props: props.params, 42 + }) 43 + } 44 + </script> 45 + 46 + <template> 47 + <a class="app-link" :href="href" @click="handleClick" :aria-label="ariaLabel"> 48 + <slot /> 49 + </a> 50 + </template> 51 + 52 + <style scoped> 53 + .app-link { 54 + text-decoration: none; 55 + color: inherit; 56 + cursor: pointer; 57 + background: none; 58 + border: none; 59 + padding: 0; 60 + font: inherit; 61 + display: inline-block; 62 + } 63 + </style>
+219
src/components/Navigation/NavItem.vue
··· 1 + <script lang="ts" setup> 2 + import { useNavigationStore } from '@/stores/navigation' 3 + import type { Page, PageNames } from '@/router' 4 + 5 + const nav = useNavigationStore() 6 + defineProps<{ item: Page }>() 7 + 8 + function go(tab: string) { 9 + const currentTab = nav.activeTab 10 + if (currentTab === tab) nav.resetTab(tab) 11 + else nav.switchTab(tab as PageNames) 12 + } 13 + 14 + const handleKeydown = (event: KeyboardEvent, tab: string) => { 15 + const tabs = document.querySelectorAll('[role="tab"]') 16 + const currentIndex = Array.from(tabs).findIndex((t) => t.getAttribute('aria-selected') === 'true') 17 + 18 + let newIndex = currentIndex 19 + 20 + switch (event.key) { 21 + case 'ArrowLeft': 22 + event.preventDefault() 23 + newIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1 24 + break 25 + case 'ArrowRight': 26 + event.preventDefault() 27 + newIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0 28 + break 29 + case 'Home': 30 + event.preventDefault() 31 + newIndex = 0 32 + break 33 + case 'End': 34 + event.preventDefault() 35 + newIndex = tabs.length - 1 36 + break 37 + case 'Enter': 38 + case ' ': 39 + event.preventDefault() 40 + go(tab) 41 + return 42 + } 43 + 44 + if (newIndex !== currentIndex) { 45 + ;(tabs[newIndex] as HTMLElement).focus() 46 + ;(tabs[newIndex] as HTMLElement).click() 47 + } 48 + } 49 + </script> 50 + 51 + <template> 52 + <button 53 + class="link" 54 + @click="go(item.name)" 55 + @keydown="handleKeydown($event, item.name)" 56 + :class="{ active: nav.activeTab === item.name }" 57 + role="tab" 58 + :aria-selected="nav.activeTab === item.name" 59 + :aria-controls="`tabpanel-${item.name}`" 60 + :id="`tab-${item.name}`" 61 + :tabindex="nav.activeTab === item.name ? 0 : -1" 62 + > 63 + <span class="icon-container" aria-hidden="true"> 64 + <component :is="item.icon" class="icon" /> 65 + </span> 66 + <span class="label">{{ item.label }}</span> 67 + </button> 68 + </template> 69 + 70 + <style lang="scss" scoped> 71 + /* default state */ 72 + .link { 73 + flex: 1; 74 + display: flex; 75 + flex-direction: column; 76 + align-items: center; 77 + text-decoration: none; 78 + padding: 0.5rem; 79 + 80 + background: none; 81 + border: none; 82 + 83 + .label { 84 + font-size: 0.75rem; 85 + color: hsl(var(--text)); 86 + } 87 + 88 + .icon-container { 89 + position: relative; 90 + display: flex; 91 + justify-content: center; 92 + align-items: center; 93 + width: 100%; 94 + 95 + width: 1.25rem; 96 + height: 1.75rem; 97 + 98 + .icon { 99 + width: 1.25rem; 100 + height: 1.25rem; 101 + aspect-ratio: 1 / 1; 102 + color: hsl(var(--subtext0)); 103 + svg { 104 + width: 100%; 105 + height: 100%; 106 + aspect-ratio: 1 / 1; 107 + } 108 + } 109 + &::before, 110 + &::after { 111 + content: ''; 112 + z-index: -1; 113 + position: absolute; 114 + height: 100%; 115 + bottom: 0; 116 + left: 50%; 117 + opacity: 0; 118 + min-width: 0.5rem; 119 + border-radius: 5rem; 120 + transform: translateX(-50%); 121 + transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); 122 + } 123 + &::before { 124 + min-width: 1rem; 125 + background-color: hsla(var(--accent) / 0.2); 126 + } 127 + &::after { 128 + min-width: 3rem; 129 + background-color: hsl(var(--text)); 130 + } 131 + } 132 + } 133 + 134 + /* active state */ 135 + .link.active { 136 + .label { 137 + color: hsl(var(--text)); 138 + } 139 + .icon-container .icon { 140 + color: hsl(var(--text)); 141 + } 142 + 143 + .icon-container::before { 144 + min-width: 3rem; 145 + opacity: 1; 146 + background-color: hsla(var(--accent) / 0.25); 147 + } 148 + } 149 + 150 + /* state layer */ 151 + .link { 152 + &:hover { 153 + .icon-container::after { 154 + opacity: 0.06; 155 + } 156 + &:active .icon-container::after { 157 + opacity: 0.08; 158 + } 159 + } 160 + &:focus-visible .icon-container::after { 161 + opacity: 0.1; 162 + } 163 + } 164 + 165 + @media (min-width: 1024px) { 166 + .link { 167 + width: 100%; 168 + flex-direction: row; 169 + justify-content: flex-start; 170 + padding: 0.5rem 1.25rem; 171 + border-radius: 10rem; 172 + text-align: left; 173 + gap: 0.5rem; 174 + 175 + --bg-strength: 0%; 176 + 177 + background-color: color-mix( 178 + in srgb, 179 + hsla(var(--accent) / 1) var(--bg-strength), 180 + hsla(var(--surface0) / 0.25) 181 + ); 182 + 183 + &:not(:hover):not(.active) { 184 + background-color: transparent; 185 + } 186 + 187 + .label { 188 + flex: 1; 189 + font-size: 0.875rem; 190 + color: hsl(var(--text)); 191 + font-weight: 700; 192 + } 193 + .icon-container { 194 + &::before, 195 + &::after { 196 + display: none; 197 + } 198 + } 199 + 200 + &:hover { 201 + --bg-strength: 5%; 202 + &:active { 203 + --bg-strength: 2.5%; 204 + } 205 + } 206 + 207 + &.active { 208 + --bg-strength: 15%; 209 + 210 + &:hover { 211 + --bg-strength: 20%; 212 + } 213 + &:active { 214 + --bg-strength: 15%; 215 + } 216 + } 217 + } 218 + } 219 + </style>
+138
src/components/Navigation/NavigationBar.vue
··· 1 + <script setup lang="ts"> 2 + import { ref } from 'vue' 3 + import NavigationItem from './NavItem.vue' 4 + import SVG from '@/components/UI/SVG.vue' 5 + import { stackRoots } from '@/router/index' 6 + import ScillaLogo from '@/assets/icons/scilla.svg?raw' 7 + 8 + import { useNavigationStore } from '@/stores/navigation' 9 + const nav = useNavigationStore() 10 + 11 + const navRef = ref<HTMLElement>() 12 + defineExpose({ $el: navRef }) 13 + </script> 14 + 15 + <template> 16 + <nav 17 + ref="navRef" 18 + class="navigation-bar" 19 + id="navigation-bar" 20 + role="tablist" 21 + aria-label="Main navigation" 22 + > 23 + <ul class="menu" role="presentation"> 24 + <li class="desktop-logo" role="presentation"> 25 + <SVG :icon="ScillaLogo"></SVG> 26 + </li> 27 + 28 + <NavigationItem 29 + v-for="route in stackRoots" 30 + :item="route" 31 + :key="route.name" 32 + role="tab" 33 + :aria-selected="nav.activeTab === route.name" 34 + :tabindex="nav.activeTab === route.name ? 0 : -1" 35 + /> 36 + </ul> 37 + </nav> 38 + </template> 39 + 40 + <style scoped lang="scss"> 41 + .navigation-bar { 42 + --gap: 0.5rem; 43 + background: hsla(var(--mantle) / 1); 44 + border-top: 1px solid hsla(var(--surface2) / 0.25); 45 + 46 + width: 100%; 47 + max-width: 100vw; 48 + padding-bottom: calc(var(--safe-area-inset-bottom) + 0.25rem); 49 + padding-top: 0.25rem; 50 + 51 + position: fixed; 52 + bottom: 0; 53 + z-index: 100; 54 + transition-property: 55 + color, background-color, box-shadow, outline, border-color, border-radius, font-weight, opacity, 56 + backdrop-filter, filter, width, bottom, left; 57 + 58 + .desktop-logo { 59 + display: none; 60 + color: white; 61 + 62 + :deep(svg) { 63 + background: hsla(var(--accent) / 0.05); 64 + color: hsla(var(--accent) / 1); 65 + border-radius: var(--radius-sm); 66 + &:hover { 67 + transform: scale(1.1); 68 + background: hsla(var(--accent) / 0.1); 69 + } 70 + &:active { 71 + transform: scale(0.95); 72 + } 73 + } 74 + } 75 + 76 + .menu { 77 + display: flex; 78 + justify-content: space-around; 79 + align-items: center; 80 + max-width: 500px; 81 + margin: 0 auto; 82 + list-style: none; 83 + gap: var(--gap); 84 + padding: 0 1rem; 85 + } 86 + 87 + @media (min-width: 640px) { 88 + padding: 1rem 0.5rem; 89 + position: relative; 90 + bottom: auto; 91 + left: auto; 92 + width: fit-content; 93 + height: 100%; 94 + border-top: none; 95 + background: transparent; 96 + backdrop-filter: none; 97 + order: -1; 98 + 99 + .desktop-logo { 100 + display: flex; 101 + justify-content: center; 102 + padding: 0.5rem; 103 + 104 + :deep(svg) { 105 + width: 2.5rem; 106 + height: 2.5rem; 107 + display: block; 108 + } 109 + } 110 + 111 + .menu { 112 + flex-direction: column; 113 + justify-content: flex-start; 114 + gap: 0.5rem; 115 + width: 100%; 116 + padding: 0; 117 + } 118 + } 119 + 120 + @media (min-width: 1024px) { 121 + width: 260px; 122 + padding-right: 1.5rem; 123 + .menu { 124 + align-items: stretch; 125 + } 126 + 127 + .desktop-logo { 128 + justify-content: flex-start; 129 + padding-left: 1rem; 130 + 131 + :deep(svg) { 132 + width: 2.5rem; 133 + height: 2.5rem; 134 + } 135 + } 136 + } 137 + } 138 + </style>
+138
src/components/Navigation/PageLayout.vue
··· 1 + <script setup lang="ts"> 2 + import { nextTick, ref, onMounted } from "vue"; 3 + import { useScrollHide } from "@/composables/useScrollHide"; 4 + 5 + import AppBar from "./AppBar.vue"; 6 + import NavigationBar from "./NavigationBar.vue"; 7 + 8 + const props = defineProps<{ 9 + title: string; 10 + }>(); 11 + 12 + const key = 13 + Math.random().toString(36).substring(2, 15) + 14 + Math.random().toString(36).substring(2, 15); 15 + 16 + const pageContent = ref<HTMLElement | null>(null); 17 + const appBar = ref<InstanceType<typeof AppBar> | null>(null); 18 + const navBar = ref<InstanceType<typeof NavigationBar> | null>(null); 19 + 20 + const announcePageChange = () => { 21 + const announcement = document.createElement("div"); 22 + announcement.setAttribute("aria-live", "polite"); 23 + announcement.setAttribute("aria-atomic", "true"); 24 + announcement.className = "sr-only"; 25 + announcement.textContent = `Navigated to ${props.title}`; 26 + document.body.appendChild(announcement); 27 + 28 + setTimeout(() => { 29 + document.body.removeChild(announcement); 30 + }, 1000); 31 + }; 32 + 33 + onMounted(async () => { 34 + await nextTick(); 35 + if (!pageContent.value) return; 36 + 37 + const scrollHide = useScrollHide({ 38 + scrollContainer: pageContent.value, 39 + appBarEl: appBar.value?.$el, 40 + navBarEl: navBar.value?.$el, 41 + }); 42 + 43 + scrollHide.measureElements(); 44 + scrollHide.attachScrollListener(); 45 + 46 + const skipToContent = document.querySelector("#skip-to-content"); 47 + if (document.activeElement === skipToContent) { 48 + pageContent.value.focus(); 49 + } else { 50 + pageContent.value.setAttribute("tabindex", "-1"); 51 + pageContent.value.focus(); 52 + } 53 + 54 + announcePageChange(); 55 + }); 56 + </script> 57 + 58 + <template> 59 + <div class="page-layout" :id="key"> 60 + <AppBar :title="title" ref="appBar"/> 61 + <main class="page-content" ref="pageContent"> 62 + <div class="content-container"> 63 + <slot/> 64 + </div> 65 + </main> 66 + </div> 67 + </template> 68 + 69 + <style> 70 + .page-layout { 71 + display: flex; 72 + flex-direction: column; 73 + max-height: 100%; 74 + height: 100%; 75 + overflow: hidden; 76 + background-color: hsl(var(--base)); 77 + position: relative; 78 + margin: 0 auto; 79 + border: 1px solid transparent; 80 + } 81 + 82 + .page-content { 83 + flex: 1; 84 + padding-top: calc(var(--safe-area-inset-top, 4.5rem)); 85 + -webkit-overflow-scrolling: touch; 86 + height: 100%; 87 + overflow-y: scroll; 88 + 89 + display: flex; 90 + flex-direction: column; 91 + align-items: center; 92 + 93 + .content-container { 94 + width: 100%; 95 + max-width: 800px; 96 + padding: 0 1rem; 97 + 98 + h1, 99 + h2, 100 + h3, 101 + h4, 102 + h5, 103 + h6 { 104 + color: rgb(var(--text)); 105 + margin-bottom: 0; 106 + } 107 + h1 { 108 + font-size: 2rem; 109 + font-weight: bolder; 110 + } 111 + h2 { 112 + font-size: 1.5rem; 113 + font-weight: bolder; 114 + } 115 + } 116 + } 117 + 118 + @media (min-width: 640px) { 119 + .page-layout { 120 + border-radius: 1rem; 121 + height: calc(100vh - 0.5rem); 122 + margin: 0.25rem; 123 + border: 1px solid hsla(var(--surface2) / 0.2); 124 + } 125 + } 126 + 127 + .sr-only { 128 + position: absolute; 129 + width: 1px; 130 + height: 1px; 131 + padding: 0; 132 + margin: -1px; 133 + overflow: hidden; 134 + clip: rect(0, 0, 0, 0); 135 + white-space: nowrap; 136 + border: 0; 137 + } 138 + </style>
+230
src/components/Navigation/TabStack.vue
··· 1 + <script lang="ts" setup> 2 + import { computed, ref, watch, defineAsyncComponent } from "vue"; 3 + import { useNavigationStore } from "@/stores/navigation"; 4 + import { pages, type StackRootNames } from "@/router"; 5 + import { useEnvironmentStore } from "@/stores/environment"; 6 + 7 + const props = defineProps<{ tab: StackRootNames }>(); 8 + const nav = useNavigationStore(); 9 + const env = useEnvironmentStore(); 10 + 11 + const stack = computed(() => nav.stacks[props.tab]); 12 + 13 + const registry: Record<string, any> = pages.reduce( 14 + (acc, page) => { 15 + const comp = page.component; 16 + acc[page.name] = 17 + typeof comp === "function" 18 + ? defineAsyncComponent({ 19 + loader: comp as unknown as () => Promise<any>, 20 + }) 21 + : comp; 22 + return acc; 23 + }, 24 + {} as Record<string, any>, 25 + ); 26 + 27 + const isAnimating = ref(false); 28 + const animationType = ref<"push" | "pop" | null>(null); 29 + const previousStackLength = ref(stack.value?.length || 0); 30 + 31 + const visualTopIndex = computed(() => { 32 + if (nav.pendingPop?.tab === props.tab) 33 + return stack.value?.length ? stack.value.length - 2 : 0; 34 + return stack.value?.length ? stack.value.length - 1 : 0; 35 + }); 36 + 37 + const shouldAnimate = computed(() => { 38 + return env.isMobile && !env.prefersReducedMotion; 39 + }); 40 + 41 + watch( 42 + () => stack.value?.length, 43 + (newLength, oldLength) => { 44 + if (!newLength || !oldLength) return; 45 + 46 + if (nav.activeTab !== props.tab) { 47 + previousStackLength.value = newLength; 48 + return; 49 + } 50 + 51 + if (newLength > oldLength && shouldAnimate.value) { 52 + animationType.value = "push"; 53 + isAnimating.value = true; 54 + 55 + setTimeout(() => { 56 + isAnimating.value = false; 57 + animationType.value = null; 58 + }, 300); 59 + } 60 + 61 + previousStackLength.value = newLength; 62 + }, 63 + ); 64 + 65 + watch( 66 + () => nav.pendingPop, 67 + (pendingPop) => { 68 + if ( 69 + !pendingPop || 70 + pendingPop.tab !== props.tab || 71 + nav.activeTab !== props.tab 72 + ) { 73 + return; 74 + } 75 + 76 + if (!shouldAnimate.value) { 77 + nav.completePop(); 78 + return; 79 + } 80 + 81 + animationType.value = "pop"; 82 + isAnimating.value = true; 83 + 84 + setTimeout(() => { 85 + isAnimating.value = false; 86 + animationType.value = null; 87 + nav.completePop(); 88 + }, 300); 89 + }, 90 + { immediate: true }, 91 + ); 92 + </script> 93 + 94 + <template> 95 + <div 96 + :data-tab="props.tab" 97 + :id="`tabpanel-${props.tab}`" 98 + :aria-hidden="nav.activeTab !== props.tab" 99 + :aria-labelledby="`tab-${props.tab}`" 100 + class="tab-stack" 101 + aria-role="tabpanel" 102 + > 103 + <template v-if="stack"> 104 + <div 105 + v-for="(entry, index) in stack" 106 + :key="entry.id" 107 + :class="[ 108 + 'stack-page', 109 + { 110 + 'is-visible': 111 + index === stack.length - 1 || 112 + index === visualTopIndex || 113 + index === visualTopIndex - 1, 114 + 115 + 'is-visual-top': index === visualTopIndex, 116 + 'is-below-visual-top': index === visualTopIndex - 1, 117 + 'is-animating': isAnimating, 118 + 'push-enter': 119 + isAnimating && 120 + animationType === 'push' && 121 + index === stack.length - 1, 122 + 'pop-exit': 123 + isAnimating && 124 + animationType === 'pop' && 125 + index === stack.length - 1, 126 + }, 127 + 128 + ]" 129 + :data-entry-id="entry.id" 130 + :data-index="index" 131 + :aria-hidden="index !== visualTopIndex" 132 + :inert="index !== visualTopIndex" 133 + > 134 + <Suspense> 135 + <template #default> 136 + <component :is="registry[entry.page]" v-bind="entry.props"/> 137 + </template> 138 + <template #fallback> 139 + <div class="page-loading" aria-hidden="true"></div> 140 + </template> 141 + </Suspense> 142 + </div> 143 + </template> 144 + </div> 145 + </template> 146 + 147 + <style scoped> 148 + .tab-stack { 149 + position: absolute; 150 + inset: 0; 151 + } 152 + 153 + .stack-page { 154 + position: absolute; 155 + inset: 0; 156 + box-sizing: border-box; 157 + 158 + display: none; 159 + transform: translateY(100%); 160 + transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); 161 + 162 + &.no-motion { 163 + display: block; 164 + transform: none !important; 165 + animation: none !important; 166 + transition: none !important; 167 + } 168 + 169 + &.is-visible { 170 + display: block; 171 + } 172 + 173 + &.is-visual-top { 174 + transform: translateY(0); 175 + z-index: 10; 176 + } 177 + 178 + &.is-below-visual-top { 179 + transform: translateY(0); 180 + z-index: 9; 181 + } 182 + 183 + &.push-enter { 184 + transform: translateY(9%); 185 + z-index: 11; 186 + animation: slideUp 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards; 187 + } 188 + 189 + &.pop-exit { 190 + transform: translateY(0); 191 + z-index: 11; 192 + animation: slideDown 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards; 193 + } 194 + } 195 + 196 + @keyframes slideUp { 197 + from { 198 + transform: translateY(20%); 199 + opacity: 0; 200 + } 201 + to { 202 + transform: translateY(0); 203 + opacity: 1; 204 + } 205 + } 206 + 207 + @keyframes slideDown { 208 + from { 209 + transform: translateY(0); 210 + opacity: 1; 211 + } 212 + to { 213 + transform: translateY(20%); 214 + opacity: 0; 215 + } 216 + } 217 + 218 + @media (prefers-reduced-motion: reduce) { 219 + .stack-page { 220 + transition: none; 221 + } 222 + 223 + .stack-page.push-enter, 224 + .stack-page.pop-exit { 225 + animation: none; 226 + transform: translateY(0); 227 + opacity: 1; 228 + } 229 + } 230 + </style>
+206
src/components/UI/BaseButton.vue
··· 1 + <script setup lang="ts"> 2 + withDefaults( 3 + defineProps<{ 4 + variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'subtle' | 'subtle-alt' 5 + size?: 'sm' | 'md' | 'lg' 6 + icon?: boolean 7 + block?: boolean 8 + loading?: boolean 9 + disabled?: boolean 10 + type?: 'button' | 'submit' | 'reset' 11 + }>(), 12 + { 13 + variant: 'primary', 14 + size: 'md', 15 + type: 'button', 16 + disabled: false, 17 + }, 18 + ) 19 + 20 + const emit = defineEmits<{ 21 + (e: 'click', event: MouseEvent): void 22 + }>() 23 + </script> 24 + 25 + <template> 26 + <button 27 + :type="type" 28 + class="th-btn" 29 + :class="[ 30 + `variant-${variant}`, 31 + `size-${size}`, 32 + { 'is-icon': icon, 'is-block': block, 'is-loading': loading }, 33 + ]" 34 + :disabled="disabled || loading" 35 + @click.stop="emit('click', $event)" 36 + > 37 + <span v-if="loading" class="spinner"></span> 38 + <slot v-else /> 39 + </button> 40 + </template> 41 + 42 + <style scoped> 43 + .th-btn { 44 + display: inline-flex; 45 + align-items: center; 46 + justify-content: center; 47 + gap: 0.5rem; 48 + border: 1px solid transparent; 49 + border-radius: var(--radius-md); 50 + font-family: inherit; 51 + font-weight: 600; 52 + line-height: 1; 53 + cursor: pointer; 54 + position: relative; 55 + overflow: hidden; 56 + 57 + &:disabled { 58 + opacity: 0.5; 59 + cursor: not-allowed; 60 + pointer-events: none; 61 + } 62 + 63 + &.is-block { 64 + width: 100%; 65 + display: flex; 66 + } 67 + 68 + background-color: hsla(var(--bg-colour) / 0.9); 69 + color: hsl(var(--text-colour)); 70 + border-color: hsla(var(--border-colour) / 0.2); 71 + 72 + &:hover:not(:disabled) { 73 + background-color: hsla(var(--bg-colour) / 1); 74 + border-color: hsla(var(--border-colour) / 0.5); 75 + } 76 + 77 + &:active:not(:disabled) { 78 + background-color: hsla(var(--bg-colour) / 0.7); 79 + border-color: hsla(var(--border-colour) / 0.5); 80 + } 81 + } 82 + 83 + /* Sizes */ 84 + .size-sm { 85 + font-size: 0.75rem; 86 + padding: 0.375rem 0.75rem; 87 + &.is-icon { 88 + padding: 0; 89 + width: 2rem; 90 + height: 2rem; 91 + } 92 + } 93 + .size-md { 94 + font-size: 0.875rem; 95 + padding: 0.625rem 1.25rem; 96 + &.is-icon { 97 + padding: 0; 98 + width: 2.5rem; 99 + height: 2.5rem; 100 + } 101 + } 102 + .size-lg { 103 + font-size: 1rem; 104 + padding: 0.875rem 1.75rem; 105 + &.is-icon { 106 + padding: 0; 107 + width: 3rem; 108 + height: 3rem; 109 + } 110 + } 111 + 112 + .variant-primary { 113 + --bg-colour: var(--accent); 114 + --text-colour: var(--base); 115 + --border-colour: var(--accent); 116 + } 117 + 118 + .variant-secondary { 119 + --bg-colour: var(--surface0); 120 + --text-colour: var(--text); 121 + --border-colour: var(--surface2); 122 + } 123 + 124 + .variant-subtle { 125 + --bg-colour: var(--accent); 126 + --text-colour: var(--accent); 127 + --border-colour: var(--accent); 128 + 129 + border-color: hsla(var(--accent) / 0.05); 130 + background-color: hsla(var(--accent) / 0.05); 131 + 132 + &:hover:not(:disabled) { 133 + background-color: hsla(var(--accent) / 0.15); 134 + border-color: hsla(var(--accent) / 0.1); 135 + } 136 + &:active:not(:disabled) { 137 + background-color: hsla(var(--accent) / 0.1); 138 + border-color: hsla(var(--accent) / 0.1); 139 + } 140 + } 141 + 142 + .variant-subtle-alt { 143 + border-color: transparent; 144 + background-color: hsla(var(--subtext0) / 0.04); 145 + color: hsl(var(--subtext0)); 146 + 147 + &:hover:not(:disabled) { 148 + background-color: hsla(var(--subtext0) / 0.06); 149 + border-color: transparent; 150 + } 151 + &:active:not(:disabled) { 152 + background-color: hsla(var(--subtext0) / 0.02); 153 + border-color: transparent; 154 + } 155 + } 156 + 157 + .variant-ghost { 158 + --bg-colour: transparent; 159 + --text-colour: var(--subtext0); 160 + --border-colour: transparent; 161 + 162 + border-color: transparent; 163 + 164 + &:hover:not(:disabled) { 165 + background-color: hsla(var(--surface2) / 0.25); 166 + border-color: hsla(var(--surface2) / 0); 167 + } 168 + &:active:not(:disabled) { 169 + background-color: hsla(var(--surface1) / 0.25); 170 + border-color: hsla(var(--surface2) / 0); 171 + } 172 + } 173 + 174 + .variant-danger { 175 + --background-colour: var(--red); 176 + --text-colour: var(--red); 177 + --border-colour: var(--red); 178 + 179 + border-color: hsla(var(--red) / 0.05); 180 + background-color: hsla(var(--red) / 0.25); 181 + 182 + &:hover:not(:disabled) { 183 + background-color: hsla(var(--red) / 0.4); 184 + border-color: hsla(var(--red) / 0.3); 185 + } 186 + &:active:not(:disabled) { 187 + background-color: hsla(var(--red) / 0.3); 188 + border-color: hsla(var(--red) / 0.3); 189 + } 190 + } 191 + 192 + /* Spinner */ 193 + .spinner { 194 + width: 1em; 195 + height: 1em; 196 + border: 2px solid currentColor; 197 + border-right-color: transparent; 198 + border-radius: 50%; 199 + animation: spin 0.75s linear infinite; 200 + } 201 + @keyframes spin { 202 + to { 203 + transform: rotate(360deg); 204 + } 205 + } 206 + </style>
+22
src/components/UI/BaseCard.vue
··· 1 + <script setup lang="ts"> 2 + defineProps<{ padding?: boolean }>(); 3 + </script> 4 + 5 + <template> 6 + <div class="card" :class="{ 'has-padding': padding !== false }"> 7 + <slot/> 8 + </div> 9 + </template> 10 + 11 + <style scoped> 12 + .card { 13 + background-color: hsl(var(--surface0) / 0.5); 14 + border: 1px solid hsl(var(--surface1)); 15 + border-radius: var(--radius-lg); 16 + overflow: hidden; 17 + position: relative; 18 + } 19 + .has-padding { 20 + padding: var(--space-4); 21 + } 22 + </style>
+80
src/components/UI/BaseCheckbox.vue
··· 1 + <script setup lang="ts"> 2 + import { useId } from 'vue' 3 + import { IconCheckRounded } from '@iconify-prerendered/vue-material-symbols' 4 + 5 + defineProps<{ 6 + label?: string 7 + disabled?: boolean 8 + }>() 9 + 10 + const model = defineModel<boolean>() 11 + const id = useId() 12 + </script> 13 + 14 + <template> 15 + <div class="checkbox-wrapper" :class="{ disabled }" @click.stop="!disabled && (model = !model)"> 16 + <div 17 + class="checkbox-box" 18 + :class="{ 'is-checked': model }" 19 + role="checkbox" 20 + :aria-checked="model" 21 + :tabindex="disabled ? -1 : 0" 22 + @keydown.space.prevent="!disabled && (model = !model)" 23 + > 24 + <IconCheckRounded class="check-icon" /> 25 + </div> 26 + <label v-if="label" :for="id" class="label">{{ label }}</label> 27 + </div> 28 + </template> 29 + 30 + <style scoped> 31 + .checkbox-wrapper { 32 + display: inline-flex; 33 + align-items: center; 34 + gap: 0.75rem; 35 + cursor: pointer; 36 + user-select: none; 37 + } 38 + 39 + .checkbox-wrapper.disabled { 40 + opacity: 0.5; 41 + cursor: not-allowed; 42 + } 43 + 44 + .checkbox-box { 45 + width: 1.5rem; 46 + height: 1.5rem; 47 + border-radius: 0.5rem; 48 + border: 2px solid hsl(var(--subtext0)); 49 + background-color: hsl(var(--surface0)); 50 + display: flex; 51 + align-items: center; 52 + justify-content: center; 53 + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); 54 + color: hsl(var(--base)); 55 + } 56 + 57 + .checkbox-box.is-checked { 58 + background-color: hsl(var(--accent)); 59 + border-color: hsl(var(--accent)); 60 + } 61 + 62 + .check-icon { 63 + width: 100%; 64 + height: 100%; 65 + opacity: 0; 66 + transform: scale(0.5); 67 + transition: all 0.2s; 68 + } 69 + 70 + .checkbox-box.is-checked .check-icon { 71 + opacity: 1; 72 + transform: scale(1); 73 + } 74 + 75 + .label { 76 + color: hsl(var(--text)); 77 + font-size: 1rem; 78 + cursor: inherit; 79 + } 80 + </style>
+371
src/components/UI/BaseModal.vue
··· 1 + <script setup lang="ts"> 2 + import { computed, onMounted, onUnmounted, watch, ref, nextTick } from 'vue' 3 + import { IconCloseRounded } from '@iconify-prerendered/vue-material-symbols' 4 + import { useEnvironmentStore } from '@/stores/environment' 5 + 6 + defineProps<{ 7 + title?: string 8 + width?: string 9 + }>() 10 + 11 + const isOpen = defineModel<boolean>('open', { required: true }) 12 + 13 + const env = useEnvironmentStore() 14 + const isMobile = computed(() => env.isMobile) 15 + 16 + const modalContainerRef = ref<HTMLElement | null>(null) 17 + const modalContentRef = ref<HTMLElement | null>(null) 18 + const previousActiveElement = ref<HTMLElement | null>(null) 19 + 20 + const isDragging = ref(false) 21 + const startY = ref(0) 22 + const currentY = ref(0) 23 + const backdropOpacity = ref(1) 24 + 25 + const getFocusableElements = (): HTMLElement[] => { 26 + if (!modalContainerRef.value) return [] 27 + return Array.from( 28 + modalContainerRef.value.querySelectorAll( 29 + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', 30 + ), 31 + ) as HTMLElement[] 32 + } 33 + 34 + const trapFocus = (e: KeyboardEvent) => { 35 + if (!isOpen.value || !modalContainerRef.value) return 36 + 37 + const focusableContent = getFocusableElements() 38 + if (focusableContent.length === 0) return 39 + 40 + const firstElement = focusableContent[0] 41 + const lastElement = focusableContent[focusableContent.length - 1] 42 + 43 + if (e.shiftKey) { 44 + if (document.activeElement === firstElement) { 45 + lastElement.focus() 46 + e.preventDefault() 47 + } 48 + } else { 49 + if (document.activeElement === lastElement) { 50 + firstElement.focus() 51 + e.preventDefault() 52 + } 53 + } 54 + } 55 + 56 + const handleKeydown = (e: KeyboardEvent) => { 57 + if (e.key === 'Escape' && isOpen.value) { 58 + isOpen.value = false 59 + } 60 + if (e.key === 'Tab' && isOpen.value) { 61 + trapFocus(e) 62 + } 63 + } 64 + 65 + // Drag Event Handlers 66 + const onTouchStart = (e: TouchEvent) => { 67 + if (!isMobile.value) return 68 + // Only allow dragging from the header area or handle 69 + const target = e.target as HTMLElement 70 + if (target.closest('.modal-body') || target.closest('.modal-footer')) return 71 + 72 + isDragging.value = true 73 + startY.value = e.touches[0].clientY 74 + currentY.value = 0 75 + } 76 + 77 + const onTouchMove = (e: TouchEvent) => { 78 + if (!isDragging.value) return 79 + 80 + const deltaY = e.touches[0].clientY - startY.value 81 + if (deltaY > 0) { 82 + e.preventDefault() // Prevent scrolling while dragging down 83 + currentY.value = deltaY 84 + 85 + // Calculate opacity based on drag percentage (assuming ~500px height as threshold) 86 + const percentage = Math.max(0, 1 - deltaY / 400) 87 + backdropOpacity.value = percentage 88 + } 89 + } 90 + 91 + const onTouchEnd = () => { 92 + if (!isDragging.value) return 93 + isDragging.value = false 94 + 95 + if (currentY.value > 150) { 96 + isOpen.value = false 97 + } else { 98 + // Reset 99 + currentY.value = 0 100 + backdropOpacity.value = 1 101 + } 102 + } 103 + 104 + watch(isOpen, async (val) => { 105 + if (typeof document === 'undefined') return 106 + 107 + if (val) { 108 + // Reset drag state on open 109 + currentY.value = 0 110 + backdropOpacity.value = 1 111 + 112 + previousActiveElement.value = document.activeElement as HTMLElement 113 + document.body.style.overflow = 'hidden' 114 + 115 + await nextTick() 116 + 117 + if (modalContainerRef.value) { 118 + const focusable = getFocusableElements() 119 + if (focusable.length > 0) { 120 + const firstContentFocus = focusable.find((el) => !el.classList.contains('close-btn')) 121 + ;(firstContentFocus || focusable[0]).focus() 122 + } else { 123 + modalContainerRef.value.focus() 124 + } 125 + } 126 + } else { 127 + document.body.style.overflow = '' 128 + if (previousActiveElement.value) previousActiveElement.value.focus() 129 + } 130 + }) 131 + 132 + onMounted(() => document.addEventListener('keydown', handleKeydown)) 133 + onUnmounted(() => { 134 + document.removeEventListener('keydown', handleKeydown) 135 + document.body.style.overflow = '' 136 + }) 137 + </script> 138 + 139 + <template> 140 + <Teleport to="body"> 141 + <Transition name="fade"> 142 + <div 143 + v-if="isOpen" 144 + class="backdrop" 145 + @click="isOpen = false" 146 + aria-hidden="true" 147 + :style="{ opacity: isDragging ? backdropOpacity : undefined }" 148 + ></div> 149 + </Transition> 150 + 151 + <Transition :name="isMobile ? 'slide-up' : 'zoom'"> 152 + <div 153 + v-if="isOpen" 154 + ref="modalContainerRef" 155 + class="modal-container" 156 + :class="{ 'is-mobile': isMobile, 'is-desktop': !isMobile }" 157 + role="dialog" 158 + aria-modal="true" 159 + :aria-labelledby="title ? 'modal-title-id' : undefined" 160 + tabindex="-1" 161 + @click="isOpen = false" 162 + > 163 + <div 164 + ref="modalContentRef" 165 + class="modal-content" 166 + :style="{ 167 + maxWidth: width || '500px', 168 + transform: isMobile && currentY > 0 ? `translateY(${currentY}px)` : undefined, 169 + transition: isDragging ? 'none' : undefined, 170 + }" 171 + @click.stop 172 + @touchstart="onTouchStart" 173 + @touchmove="onTouchMove" 174 + @touchend="onTouchEnd" 175 + > 176 + <div v-if="isMobile" class="drag-handle-wrapper"> 177 + <div class="drag-handle" aria-hidden="true"></div> 178 + </div> 179 + 180 + <div class="modal-header"> 181 + <h2 v-if="title" id="modal-title-id" class="modal-title">{{ title }}</h2> 182 + 183 + <button 184 + class="close-btn" 185 + @click="isOpen = false" 186 + aria-label="Close modal" 187 + type="button" 188 + > 189 + <IconCloseRounded /> 190 + </button> 191 + </div> 192 + 193 + <div class="modal-body"> 194 + <slot /> 195 + </div> 196 + 197 + <div v-if="$slots.footer" class="modal-footer"> 198 + <slot name="footer" /> 199 + </div> 200 + </div> 201 + </div> 202 + </Transition> 203 + </Teleport> 204 + </template> 205 + 206 + <style scoped lang="scss"> 207 + .backdrop { 208 + position: fixed; 209 + inset: 0; 210 + background: hsla(var(--crust) / 0.6); 211 + backdrop-filter: blur(4px); 212 + z-index: 9998; 213 + transition: opacity 0.1s linear; 214 + } 215 + 216 + .modal-container { 217 + position: fixed; 218 + z-index: 9999; 219 + display: flex; 220 + flex-direction: column; 221 + outline-color: transparent; 222 + } 223 + 224 + .modal-content { 225 + background: hsl(var(--base)); 226 + display: flex; 227 + flex-direction: column; 228 + max-height: 90vh; 229 + width: 100%; 230 + position: relative; 231 + box-shadow: 232 + 0 10px 25px -5px rgba(0, 0, 0, 0.1), 233 + 0 8px 10px -6px rgba(0, 0, 0, 0.1); 234 + will-change: transform; 235 + } 236 + 237 + .is-desktop { 238 + inset: 0; 239 + align-items: center; 240 + justify-content: center; 241 + padding: 1rem; 242 + 243 + .modal-header { 244 + padding-top: 1.25rem; 245 + } 246 + 247 + .modal-content { 248 + border-radius: 1rem; 249 + border: 1px solid hsla(var(--surface2) / 0.2); 250 + } 251 + } 252 + 253 + .is-mobile { 254 + bottom: 0; 255 + left: 0; 256 + right: 0; 257 + justify-content: flex-end; 258 + 259 + .modal-content { 260 + border-top-left-radius: 1.5rem; 261 + border-top-right-radius: 1.5rem; 262 + padding-bottom: env(safe-area-inset-bottom, 20px); 263 + max-height: 85vh; 264 + } 265 + } 266 + 267 + .modal-header { 268 + display: flex; 269 + align-items: center; 270 + justify-content: space-between; 271 + padding: 0.5rem 1.5rem 0.5rem; 272 + flex-shrink: 0; 273 + 274 + .modal-title { 275 + font-size: 1.25rem; 276 + font-weight: 700; 277 + color: hsl(var(--text)); 278 + margin: 0; 279 + } 280 + } 281 + 282 + .drag-handle-wrapper { 283 + width: 100%; 284 + display: flex; 285 + justify-content: center; 286 + padding-top: 0.75rem; 287 + padding-bottom: 0.25rem; 288 + touch-action: none; 289 + 290 + .drag-handle { 291 + width: 3rem; 292 + height: 0.25rem; 293 + background: hsl(var(--surface2)); 294 + border-radius: 99px; 295 + } 296 + } 297 + 298 + .close-btn { 299 + background: transparent; 300 + border: none; 301 + font-size: 1.5rem; 302 + color: hsl(var(--subtext0)); 303 + cursor: pointer; 304 + 305 + width: 2rem; 306 + height: 2rem; 307 + 308 + display: flex; 309 + align-items: center; 310 + justify-content: center; 311 + 312 + padding: 0; 313 + line-height: 1; 314 + 315 + margin-left: auto; 316 + border-radius: 0.25rem; 317 + 318 + &:focus-visible { 319 + color: hsl(var(--text)); 320 + background: hsla(var(--surface0) / 0.5); 321 + } 322 + 323 + &:hover { 324 + color: hsl(var(--text)); 325 + background: hsla(var(--surface0) / 0.5); 326 + } 327 + } 328 + 329 + .modal-body { 330 + padding: 0 1.5rem 1.5rem; 331 + overflow-y: auto; 332 + flex: 1; 333 + color: hsl(var(--text)); 334 + } 335 + 336 + .modal-footer { 337 + padding: 1rem 1.5rem; 338 + border-top: 1px solid hsl(var(--surface0)); 339 + display: flex; 340 + gap: 0.5rem; 341 + justify-content: flex-end; 342 + } 343 + 344 + .fade-enter-active, 345 + .fade-leave-active { 346 + transition: opacity 0.2s ease; 347 + } 348 + .fade-enter-from, 349 + .fade-leave-to { 350 + opacity: 0; 351 + } 352 + 353 + .zoom-enter-active, 354 + .zoom-leave-active { 355 + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); 356 + } 357 + .zoom-enter-from, 358 + .zoom-leave-to { 359 + opacity: 0; 360 + transform: scale(0.95); 361 + } 362 + 363 + .slide-up-enter-active, 364 + .slide-up-leave-active { 365 + transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1); 366 + } 367 + .slide-up-enter-from, 368 + .slide-up-leave-to { 369 + transform: translateY(100%); 370 + } 371 + </style>
+31
src/components/UI/ListGroup.vue
··· 1 + <script setup lang="ts"> 2 + defineProps<{ title?: string }>() 3 + </script> 4 + 5 + <template> 6 + <div class="list-group-wrapper"> 7 + <h3 v-if="title" class="list-header text-caption">{{ title }}</h3> 8 + <div class="list-group"> 9 + <slot /> 10 + </div> 11 + </div> 12 + </template> 13 + 14 + <style scoped> 15 + .list-group-wrapper { 16 + margin-bottom: var(--space-6); 17 + } 18 + .list-header { 19 + padding: 0 var(--space-4) var(--space-2); 20 + margin-left: var(--space-1); 21 + } 22 + .list-group { 23 + background-color: hsl(var(--surface0)); 24 + border: 1px solid hsla(var(--surface2) / 0.5); 25 + border-radius: var(--radius-xl); 26 + overflow: hidden; 27 + display: flex; 28 + flex-direction: column; 29 + box-shadow: 0 4px 20px -4px hsla(var(--crust) / 0.1); 30 + } 31 + </style>
+175
src/components/UI/ListItem.vue
··· 1 + <script setup lang="ts"> 2 + import { computed } from 'vue' 3 + import AppLink from '@/components/Navigation/AppLink.vue' 4 + import { IconChevronRightRounded } from '@iconify-prerendered/vue-material-symbols' 5 + 6 + const props = defineProps<{ 7 + title?: string 8 + subtitle?: string 9 + chevron?: boolean 10 + clickable?: boolean 11 + danger?: boolean 12 + 13 + href?: string 14 + to?: string | object 15 + target?: string 16 + }>() 17 + 18 + const emit = defineEmits<{ 19 + (e: 'click', event: MouseEvent): void 20 + }>() 21 + 22 + const isLink = computed(() => !!props.href || !!props.to) 23 + const isInteractive = computed(() => props.clickable || isLink.value) 24 + 25 + const componentType = computed(() => { 26 + if (props.to) return AppLink 27 + if (props.href) return 'a' 28 + return 'div' 29 + }) 30 + 31 + function handleClick(e: MouseEvent) { 32 + if (isInteractive.value) emit('click', e) 33 + } 34 + 35 + function handleKeydown(e: KeyboardEvent) { 36 + if (!isInteractive.value) return 37 + if (!isLink.value && (e.key === 'Enter' || e.key === ' ')) { 38 + e.preventDefault() 39 + emit('click', e as unknown as MouseEvent) 40 + } 41 + } 42 + </script> 43 + 44 + <template> 45 + <component 46 + :is="componentType" 47 + class="list-item" 48 + :class="{ 'is-clickable': isInteractive, 'is-danger': danger }" 49 + :href="href" 50 + :to="to" 51 + :target="target" 52 + :tabindex="isInteractive ? 0 : -1" 53 + :role="!isLink && isInteractive ? 'button' : undefined" 54 + @click="handleClick" 55 + @keydown="handleKeydown" 56 + > 57 + <div v-if="$slots.start" class="item-start"> 58 + <slot name="start" /> 59 + </div> 60 + 61 + <div class="item-content"> 62 + <div v-if="title" class="item-title">{{ title }}</div> 63 + <div v-if="subtitle" class="item-subtitle">{{ subtitle }}</div> 64 + <slot /> 65 + </div> 66 + 67 + <div v-if="$slots.end || chevron" class="item-end"> 68 + <slot name="end" /> 69 + <IconChevronRightRounded v-if="chevron" class="chevron" /> 70 + </div> 71 + </component> 72 + </template> 73 + 74 + <style scoped lang="scss"> 75 + .list-item { 76 + position: relative; 77 + display: flex; 78 + align-items: center; 79 + gap: var(--space-3); 80 + padding: var(--space-4); 81 + background: hsl(var(--surface0)); 82 + transition: background-color 0.2s ease; 83 + text-decoration: none; 84 + color: inherit; 85 + min-height: 3.5rem; 86 + outline: none; 87 + 88 + &::after { 89 + content: ''; 90 + position: absolute; 91 + bottom: 0; 92 + right: 0; 93 + left: 1rem; 94 + height: 1px; 95 + background-color: hsla(var(--surface2) / 0.5); 96 + } 97 + 98 + &:last-child::after { 99 + display: none; 100 + } 101 + } 102 + 103 + .is-clickable { 104 + cursor: pointer; 105 + &:hover { 106 + background: hsl(var(--surface1)); 107 + } 108 + &:active { 109 + background: hsl(var(--surface2)); 110 + } 111 + &:focus-visible { 112 + z-index: 1; 113 + background: hsl(var(--surface1)); 114 + box-shadow: inset 0 0 0 2px hsl(var(--accent)); 115 + } 116 + } 117 + 118 + .is-danger { 119 + .item-title, 120 + .item-start, 121 + .chevron { 122 + color: hsl(var(--red)); 123 + } 124 + } 125 + 126 + .item-start { 127 + display: flex; 128 + align-items: center; 129 + justify-content: center; 130 + font-size: 1.5rem; 131 + color: hsl(var(--accent)); 132 + 133 + svg { 134 + display: block; 135 + } 136 + } 137 + 138 + .item-content { 139 + flex: 1; 140 + display: flex; 141 + flex-direction: column; 142 + justify-content: center; 143 + min-width: 0; 144 + gap: 2px; 145 + } 146 + 147 + .item-title { 148 + font-weight: 600; 149 + color: hsl(var(--text)); 150 + font-size: 1rem; 151 + white-space: nowrap; 152 + overflow: hidden; 153 + text-overflow: ellipsis; 154 + } 155 + 156 + .item-subtitle { 157 + font-size: 0.8rem; 158 + color: hsl(var(--subtext0)); 159 + line-height: 1.3; 160 + } 161 + 162 + .item-end { 163 + display: flex; 164 + align-items: center; 165 + gap: var(--space-2); 166 + color: hsl(var(--subtext1)); 167 + font-size: 0.9rem; 168 + font-weight: 500; 169 + } 170 + 171 + .chevron { 172 + font-size: 1.25rem; 173 + opacity: 0.4; 174 + } 175 + </style>
+9
src/components/UI/SVG.vue
··· 1 + <script lang="ts" setup> 2 + defineProps<{ 3 + icon: string 4 + }>() 5 + </script> 6 + 7 + <template> 8 + <div v-html="icon" style="display: flex"></div> 9 + </template>
+32
src/components/UI/SkeletonLoader.vue
··· 1 + <script setup lang="ts"> 2 + defineProps<{ 3 + width?: string; 4 + height?: string; 5 + circle?: boolean; 6 + }>(); 7 + </script> 8 + 9 + <template> 10 + <div class="skeleton" :class="{ circle }" :style="{ width, height }"></div> 11 + </template> 12 + 13 + <style scoped> 14 + .skeleton { 15 + background-color: hsl(var(--surface0)); 16 + border-radius: var(--radius-sm); 17 + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; 18 + } 19 + .circle { 20 + border-radius: 50%; 21 + } 22 + 23 + @keyframes pulse { 24 + 0%, 25 + 100% { 26 + opacity: 1; 27 + } 28 + 50% { 29 + opacity: 0.5; 30 + } 31 + } 32 + </style>
+68
src/components/UI/TextArea.vue
··· 1 + <script setup lang="ts"> 2 + import { useId } from 'vue' 3 + 4 + defineProps<{ 5 + label?: string 6 + placeholder?: string 7 + rows?: number 8 + error?: string 9 + }>() 10 + 11 + const model = defineModel<string>() 12 + const id = useId() 13 + </script> 14 + 15 + <template> 16 + <div class="input-group"> 17 + <label v-if="label" :for="id" class="label">{{ label }}</label> 18 + <textarea 19 + :id="id" 20 + v-model="model" 21 + :rows="rows || 3" 22 + :placeholder="placeholder" 23 + class="input textarea" 24 + :class="{ 'has-error': error }" 25 + ></textarea> 26 + <span v-if="error" class="error-text">{{ error }}</span> 27 + </div> 28 + </template> 29 + 30 + <style scoped> 31 + .input-group { 32 + display: flex; 33 + flex-direction: column; 34 + gap: 0.25rem; 35 + width: 100%; 36 + margin-bottom: 1rem; 37 + } 38 + .label { 39 + font-size: 0.875rem; 40 + font-weight: 600; 41 + color: hsl(var(--subtext1)); 42 + margin-left: 0.25rem; 43 + } 44 + .input { 45 + width: 100%; 46 + padding: 0.75rem 1rem; 47 + border-radius: 0.75rem; 48 + border: 1px solid transparent; 49 + background-color: hsla(var(--surface0) / 0.3); 50 + border: 1px solid hsla(var(--surface2) / 0.5); 51 + color: hsl(var(--text)); 52 + font-size: 1rem; 53 + font-family: inherit; 54 + resize: vertical; 55 + 56 + &:focus-visible { 57 + background-color: hsla(var(--base) / 0.9); 58 + } 59 + &.has-error { 60 + border-color: hsl(var(--red)); 61 + } 62 + } 63 + .error-text { 64 + font-size: 0.75rem; 65 + color: hsl(var(--red)); 66 + margin-left: 0.25rem; 67 + } 68 + </style>
+124
src/components/UI/TextInput.vue
··· 1 + <script setup lang="ts"> 2 + import { useId, useSlots } from 'vue' 3 + 4 + defineProps<{ 5 + label?: string 6 + placeholder?: string 7 + type?: 'text' | 'password' | 'email' | 'number' | 'search' 8 + error?: string 9 + }>() 10 + 11 + const model = defineModel<string | number>() 12 + const id = useId() 13 + const slots = useSlots() 14 + </script> 15 + 16 + <template> 17 + <div class="input-group"> 18 + <label v-if="label" :for="id" class="label"> 19 + {{ label }} 20 + </label> 21 + <div class="input-wrapper"> 22 + <div v-if="slots.prefix" class="prefix-wrapper"> 23 + <slot name="prefix"></slot> 24 + </div> 25 + <input 26 + :id="id" 27 + v-model="model" 28 + :type="type || 'text'" 29 + :placeholder="placeholder" 30 + :class="{ input: true, 'has-prefix': !!slots.prefix, 'has-error': !!error }" 31 + /> 32 + </div> 33 + <span v-if="error" class="error-text">{{ error }}</span> 34 + </div> 35 + </template> 36 + 37 + <style scoped lang="scss"> 38 + .input-group { 39 + display: flex; 40 + flex-direction: column; 41 + gap: 0.375rem; 42 + width: 100%; 43 + margin-bottom: 1rem; 44 + } 45 + 46 + .label { 47 + font-size: 0.875rem; 48 + font-weight: 600; 49 + color: hsl(var(--text)); 50 + margin-left: 0.125rem; 51 + } 52 + 53 + .input-wrapper { 54 + position: relative; 55 + width: 100%; 56 + 57 + display: flex; 58 + align-items: center; 59 + 60 + border-radius: var(--radius-md); 61 + border: 1px solid hsla(var(--surface2) / 0.5); 62 + background-color: hsl(var(--surface0) / 0.3); 63 + 64 + outline: 2px solid transparent; 65 + outline-offset: 4px; 66 + 67 + &:hover { 68 + background-color: hsl(var(--surface0)); 69 + border-color: hsl(var(--surface2)); 70 + } 71 + &:focus-within { 72 + background-color: hsl(var(--base)); 73 + outline-color: hsl(var(--accent)); 74 + outline-offset: 2px; 75 + } 76 + &:has(.has-error) { 77 + border-color: hsl(var(--red)); 78 + color: hsl(var(--red)); 79 + &:focus-visible { 80 + box-shadow: 0 0 0 3px hsla(var(--red) / 0.15); 81 + } 82 + } 83 + } 84 + 85 + .input { 86 + color: hsl(var(--text)); 87 + font-size: 1rem; 88 + font-family: inherit; 89 + background: transparent; 90 + border: none; 91 + width: 100%; 92 + padding: 0.75rem 1rem; 93 + 94 + outline: none; 95 + 96 + &.has-prefix { 97 + padding-left: 0; 98 + } 99 + 100 + &:focus-visible { 101 + outline: none; 102 + } 103 + 104 + &::placeholder { 105 + color: hsl(var(--overlay0)); 106 + } 107 + } 108 + 109 + .prefix-wrapper { 110 + height: 100%; 111 + display: flex; 112 + align-items: center; 113 + color: hsl(var(--overlay0)); 114 + min-width: 2.5rem; 115 + justify-content: center; 116 + } 117 + 118 + .error-text { 119 + font-size: 0.75rem; 120 + color: hsl(var(--red)); 121 + font-weight: 600; 122 + margin-left: 0.125rem; 123 + } 124 + </style>
+81
src/components/UI/ToggleSwitch.vue
··· 1 + <script setup lang="ts"> 2 + import { useId } from 'vue' 3 + 4 + defineProps<{ 5 + label?: string 6 + }>() 7 + 8 + const model = defineModel<boolean>() 9 + const id = useId() 10 + 11 + const toggle = (e: MouseEvent) => { 12 + model.value = !model.value 13 + } 14 + </script> 15 + 16 + <template> 17 + <div class="toggle-wrapper" @click.stop="toggle"> 18 + <div class="label" v-if="label" :id="`${id}-label`">{{ label }}</div> 19 + <button 20 + type="button" 21 + role="switch" 22 + :aria-checked="model" 23 + :aria-labelledby="label ? `${id}-label` : undefined" 24 + class="toggle-track" 25 + :class="{ 'is-checked': model }" 26 + > 27 + <span class="toggle-thumb"></span> 28 + </button> 29 + </div> 30 + </template> 31 + 32 + <style scoped> 33 + .toggle-wrapper { 34 + display: flex; 35 + align-items: center; 36 + justify-content: space-between; 37 + gap: 1rem; 38 + cursor: pointer; 39 + } 40 + 41 + .label { 42 + color: hsl(var(--text)); 43 + font-weight: 500; 44 + } 45 + 46 + .toggle-track { 47 + width: 3.25rem; 48 + height: 1.75rem; 49 + background-color: hsl(var(--surface1)); 50 + border-radius: 99px; 51 + border: none; 52 + position: relative; 53 + cursor: pointer; 54 + flex-shrink: 0; 55 + 56 + box-sizing: unset; 57 + border: 1px solid hsl(var(--surface2)); 58 + 59 + &.is-checked { 60 + background-color: hsl(var(--accent)); 61 + 62 + .toggle-thumb { 63 + transform: translateX(1.5rem); 64 + background-color: hsl(var(--base)); 65 + } 66 + } 67 + 68 + &:focus-visible { 69 + } 70 + } 71 + 72 + .toggle-thumb { 73 + position: absolute; 74 + top: 0.125rem; 75 + left: 0.125rem; 76 + width: 1.5rem; 77 + height: 1.5rem; 78 + background-color: hsl(var(--text)); 79 + border-radius: 50%; 80 + } 81 + </style>
+141
src/composables/useScrollHide.ts
··· 1 + import { useEnvironmentStore } from "@/stores/environment"; 2 + import { nextTick, ref, watch } from "vue"; 3 + 4 + export function useScrollHide(opts: { 5 + appBarEl: HTMLElement | null; 6 + navBarEl: HTMLElement | null; 7 + scrollContainer: HTMLElement; 8 + }) { 9 + const scrollY = ref(0); 10 + const appBarHeight = ref(0); 11 + const navBarHeight = ref(0); 12 + const appBarTransform = ref(0); 13 + const navBarTransform = ref(0); 14 + 15 + const env = useEnvironmentStore(); 16 + 17 + const navBarEl = ref<HTMLElement | null>(opts.navBarEl); 18 + const { appBarEl, scrollContainer } = opts; 19 + if (!opts.navBarEl) 20 + navBarEl.value = document.getElementById("navigation-bar"); 21 + 22 + const lastScrollY = ref(0); 23 + let ticking = false; 24 + let latestScroll = 0; 25 + let ro: ResizeObserver | null = null; 26 + 27 + const clamp = (v: number, a = 0, b = Infinity) => Math.min(b, Math.max(a, v)); 28 + 29 + const measureElements = async () => { 30 + await nextTick(); 31 + if (env.isDesktop) return; 32 + 33 + if (appBarEl) appBarHeight.value = appBarEl.offsetHeight; 34 + if (navBarEl.value) { 35 + const rects = navBarEl.value.getBoundingClientRect(); 36 + navBarHeight.value = 37 + navBarEl.value.offsetHeight + (rects.bottom - rects.top); 38 + } 39 + appBarTransform.value = clamp(appBarTransform.value, 0, appBarHeight.value); 40 + navBarTransform.value = clamp(navBarTransform.value, 0, navBarHeight.value); 41 + }; 42 + 43 + const updateOnRaf = () => { 44 + if (env.isDesktop) { 45 + ticking = false; 46 + return; 47 + } 48 + 49 + const current = latestScroll; 50 + const delta = current - lastScrollY.value; 51 + lastScrollY.value = current; 52 + scrollY.value = current; 53 + 54 + appBarTransform.value = clamp( 55 + appBarTransform.value + delta, 56 + 0, 57 + appBarHeight.value, 58 + ); 59 + navBarTransform.value = clamp( 60 + navBarTransform.value + delta, 61 + 0, 62 + navBarHeight.value, 63 + ); 64 + 65 + if (appBarEl) 66 + appBarEl.style.transform = `translateY(-${appBarTransform.value}px)`; 67 + if (navBarEl.value) 68 + navBarEl.value.style.transform = `translateY(${navBarTransform.value}px)`; 69 + 70 + ticking = false; 71 + }; 72 + 73 + const handleScroll = () => { 74 + if (env.isDesktop) return; 75 + 76 + const container = scrollContainer; 77 + if (!container) return; 78 + latestScroll = container.scrollTop; 79 + 80 + if (appBarHeight.value === 0 || navBarHeight.value === 0) { 81 + measureElements(); 82 + } 83 + 84 + if (!ticking) { 85 + ticking = true; 86 + requestAnimationFrame(updateOnRaf); 87 + } 88 + }; 89 + 90 + const attachScrollListener = async () => { 91 + if (!scrollContainer) return; 92 + await measureElements(); 93 + lastScrollY.value = scrollContainer.scrollTop; 94 + scrollContainer.addEventListener("scroll", handleScroll, { passive: true }); 95 + 96 + if (!ro && (appBarEl || navBarEl)) { 97 + ro = new ResizeObserver(() => { 98 + measureElements(); 99 + }); 100 + if (appBarEl) ro.observe(appBarEl); 101 + if (navBarEl.value) ro.observe(navBarEl.value); 102 + } 103 + }; 104 + 105 + const detachScrollListener = () => { 106 + if (!scrollContainer) return; 107 + scrollContainer.removeEventListener("scroll", handleScroll); 108 + if (ro) { 109 + ro.disconnect(); 110 + ro = null; 111 + } 112 + resetTransforms(); 113 + }; 114 + 115 + const resetTransforms = () => { 116 + appBarTransform.value = 0; 117 + navBarTransform.value = 0; 118 + if (appBarEl) appBarEl.style.transform = ""; 119 + if (navBarEl.value) navBarEl.value.style.transform = ""; 120 + }; 121 + 122 + watch( 123 + () => env.isDesktop, 124 + (isDesktop) => { 125 + if (isDesktop) resetTransforms(); 126 + else measureElements(); 127 + }, 128 + { immediate: true }, 129 + ); 130 + 131 + return { 132 + scrollY, 133 + appBarTransform, 134 + navBarTransform, 135 + appBarHeight, 136 + navBarHeight, 137 + measureElements, 138 + attachScrollListener, 139 + detachScrollListener, 140 + }; 141 + }
+3
src/env.d.ts
··· 1 + /// <reference types="vite/client" /> 2 + 3 + declare const __APP_VERSION__: string
+9
src/main.ts
··· 1 + import { createApp } from "vue"; 2 + import { createPinia } from "pinia"; 3 + 4 + import "./assets/main.css"; 5 + import App from "./App.vue"; 6 + 7 + const app = createApp(App); 8 + app.use(createPinia()); 9 + app.mount("#app");
+125
src/router/index.ts
··· 1 + import { 2 + IconHomeRounded, 3 + IconSearchRounded, 4 + IconSettingsRounded, 5 + } from '@iconify-prerendered/vue-material-symbols' 6 + import type { Component } from 'vue' 7 + 8 + export type Page = { 9 + root: boolean 10 + label: string 11 + name: string 12 + path: string 13 + component: Component | (() => Promise<Component>) 14 + icon?: Component | (() => Promise<Component>) 15 + } 16 + 17 + export const pages = [ 18 + { 19 + root: true, 20 + label: 'Home', 21 + name: 'home', 22 + path: '/', 23 + component: async () => import('@/views/Root/HomeView.vue'), 24 + icon: IconHomeRounded, 25 + }, 26 + { 27 + root: true, 28 + label: 'Search', 29 + name: 'search', 30 + path: '/search', 31 + component: () => import('@/views/Root/SearchView.vue'), 32 + icon: IconSearchRounded, 33 + }, 34 + { 35 + root: false, 36 + label: 'Subpage', 37 + name: 'subpage', 38 + path: '/subpage', 39 + component: () => import('@/views/SubPage.vue'), 40 + }, 41 + { 42 + root: false, 43 + label: 'User Profile', 44 + name: 'user-profile', 45 + path: '/user/:id', 46 + component: () => import('@/views/UserProfile.vue'), 47 + }, 48 + { 49 + root: false, 50 + label: 'Login', 51 + name: 'login', 52 + path: '/login', 53 + component: () => import('@/views/Auth/LoginPage.vue'), 54 + }, 55 + { 56 + root: false, 57 + label: 'Authenticating...', 58 + name: 'oauth-callback', 59 + path: '/oauth/callback', 60 + component: () => import('@/views/Auth/OAuthCallback.vue'), 61 + }, 62 + { 63 + root: true, 64 + label: 'Settings', 65 + name: 'settings', 66 + path: '/settings', 67 + icon: IconSettingsRounded, 68 + component: () => import('@/views/SettingsPage.vue'), 69 + }, 70 + ] as const satisfies readonly Page[] 71 + 72 + export const stackRoots = pages.filter((page) => page.root) 73 + export type PageNames = (typeof pages)[number]['name'] 74 + export type StackRootNames = (typeof stackRoots)[number]['name'] 75 + 76 + export const getPageByName = (name: string) => pages.find((p) => p.name === name) 77 + 78 + function compilePath(path: string) { 79 + const paramNames: string[] = [] 80 + const regexString = 81 + '^' + 82 + path.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => { 83 + paramNames.push(key) 84 + return '([^/]+)' 85 + }) + 86 + '$' 87 + return { regex: new RegExp(regexString), paramNames } 88 + } 89 + 90 + export const matchPageByPath = (currentPath: string) => { 91 + for (const page of pages) { 92 + const { regex, paramNames } = compilePath(page.path) 93 + const match = currentPath.match(regex) 94 + 95 + if (match) { 96 + const params: Record<string, string> = {} 97 + match.slice(1).forEach((value, index) => { 98 + const param = paramNames[index] 99 + if (param) params[param] = decodeURIComponent(value) 100 + }) 101 + return { page, params } 102 + } 103 + } 104 + return null 105 + } 106 + 107 + export const compileUrl = (path: string, props: Record<string, unknown> = {}) => { 108 + let url = path 109 + const usedKeys = new Set<string>() 110 + 111 + url = url.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => { 112 + if (props[key] !== undefined) { 113 + usedKeys.add(key) 114 + return encodeURIComponent(String(props[key])) 115 + } 116 + return ':' + key 117 + }) 118 + 119 + const remainingProps: Record<string, unknown> = {} 120 + Object.keys(props).forEach((key) => { 121 + if (!usedKeys.has(key)) remainingProps[key] = props[key] 122 + }) 123 + 124 + return { url, remainingProps } 125 + }
+192
src/stores/auth.ts
··· 1 + import { defineStore } from 'pinia' 2 + import { ref, computed, markRaw } from 'vue' 3 + import { 4 + configureOAuth, 5 + createAuthorizationUrl, 6 + finalizeAuthorization, 7 + getSession, 8 + deleteStoredSession, 9 + OAuthUserAgent, 10 + type Session, 11 + } from '@atcute/oauth-browser-client' 12 + import { 13 + CompositeDidDocumentResolver, 14 + LocalActorResolver, 15 + PlcDidDocumentResolver, 16 + WebDidDocumentResolver, 17 + XrpcHandleResolver, 18 + } from '@atcute/identity-resolver' 19 + import { Client, simpleFetchHandler } from '@atcute/client' 20 + import type { ActorIdentifier } from '@atcute/lexicons' 21 + import type { DidString } from '@/types/atproto.ts' 22 + 23 + export const useAuthStore = defineStore('auth', () => { 24 + const session = ref<Session | null>(null) 25 + const agent = ref<OAuthUserAgent | null>(null) 26 + const isLoading = ref(true) 27 + const isProcessingCallback = ref(false) 28 + const error = ref<string | null>(null) 29 + 30 + const isAuthenticated = computed(() => !!session.value) 31 + const activeDid = ref<string | null>(localStorage.getItem('scilla-active-did')) 32 + const userDid = computed(() => session.value?.info.sub) 33 + 34 + function init() { 35 + configureOAuth({ 36 + metadata: { 37 + client_id: import.meta.env.VITE_OAUTH_CLIENT_ID, 38 + redirect_uri: import.meta.env.VITE_OAUTH_REDIRECT_URI, 39 + }, 40 + identityResolver: new LocalActorResolver({ 41 + handleResolver: new XrpcHandleResolver({ serviceUrl: 'https://public.api.bsky.app' }), 42 + didDocumentResolver: new CompositeDidDocumentResolver({ 43 + methods: { 44 + plc: new PlcDidDocumentResolver(), 45 + web: new WebDidDocumentResolver(), 46 + }, 47 + }), 48 + }), 49 + }) 50 + 51 + if (activeDid.value) { 52 + resumeSession(activeDid.value) 53 + } else { 54 + isLoading.value = false 55 + } 56 + } 57 + 58 + async function resumeSession(did: string) { 59 + try { 60 + isLoading.value = true 61 + const restoredSession = await getSession(did as DidString, { 62 + allowStale: true, 63 + }) 64 + session.value = restoredSession 65 + agent.value = markRaw(new OAuthUserAgent(restoredSession)) 66 + } catch (err) { 67 + console.error('Failed to resume session', err) 68 + } finally { 69 + isLoading.value = false 70 + } 71 + } 72 + 73 + async function login(input: string) { 74 + isLoading.value = true 75 + error.value = null 76 + 77 + try { 78 + const scope = import.meta.env.VITE_OAUTH_SCOPE 79 + let authUrl: URL 80 + 81 + if (input.startsWith('http://') || input.startsWith('https://')) { 82 + authUrl = await createAuthorizationUrl({ 83 + target: { type: 'pds', serviceUrl: input }, 84 + scope, 85 + }) 86 + } else { 87 + try { 88 + authUrl = await createAuthorizationUrl({ 89 + target: { type: 'account', identifier: input as ActorIdentifier }, 90 + scope, 91 + }) 92 + } catch (err) { 93 + if (err instanceof Error && err.message === 'failed to resolve handle') { 94 + authUrl = await attemptFallbackPdsLogin(input, scope) 95 + } else { 96 + console.error(err) 97 + throw err 98 + } 99 + } 100 + } 101 + 102 + window.location.assign(authUrl) 103 + } catch (err) { 104 + isLoading.value = false 105 + error.value = err instanceof Error ? err.message : 'Failed to start login' 106 + console.log(error) 107 + } 108 + } 109 + 110 + async function attemptFallbackPdsLogin(input: string, scope: string): Promise<URL> { 111 + const serviceUrl = `https://${input}` 112 + 113 + const handler = simpleFetchHandler({ service: serviceUrl }) 114 + const client = new Client({ handler }) 115 + const { ok } = await client.get('com.atproto.server.describeServer') 116 + 117 + if (!ok) throw new Error('Failed to resolve handle and server is not a valid PDS') 118 + 119 + return createAuthorizationUrl({ 120 + target: { type: 'pds', serviceUrl }, 121 + scope, 122 + }) 123 + } 124 + 125 + async function handleCallback() { 126 + if (session.value) return true 127 + if (isProcessingCallback.value) return true 128 + 129 + const hash = window.location.hash 130 + if (!hash || hash.length < 2) { 131 + return false 132 + } 133 + 134 + try { 135 + isProcessingCallback.value = true 136 + isLoading.value = true 137 + 138 + const params = new URLSearchParams(hash.slice(1)) 139 + window.history.replaceState(null, '', window.location.pathname + window.location.search) 140 + 141 + const result = await finalizeAuthorization(params) 142 + session.value = result.session 143 + agent.value = markRaw(new OAuthUserAgent(result.session)) 144 + 145 + activeDid.value = result.session.info.sub 146 + localStorage.setItem('scilla-active-did', result.session.info.sub) 147 + 148 + return true 149 + } catch (err: unknown) { 150 + console.log(err) 151 + if (err instanceof Error) error.value = err.message || 'Login failed' 152 + return false 153 + } finally { 154 + isLoading.value = false 155 + isProcessingCallback.value = false 156 + } 157 + } 158 + 159 + async function logout() { 160 + if (activeDid.value) { 161 + try { 162 + if (agent.value) await agent.value.signOut() 163 + } catch (e) { 164 + console.error('Error during sign out', e) 165 + deleteStoredSession(activeDid.value as DidString) 166 + } 167 + } 168 + 169 + session.value = null 170 + agent.value = null 171 + activeDid.value = null 172 + localStorage.removeItem('scilla-active-did') 173 + } 174 + 175 + function getRpc() { 176 + if (!agent.value) throw new Error('Not logged in') 177 + return new Client({ handler: agent.value }) 178 + } 179 + 180 + return { 181 + session, 182 + isAuthenticated, 183 + isLoading, 184 + error, 185 + userDid, 186 + init, 187 + login, 188 + handleCallback, 189 + logout, 190 + getRpc, 191 + } 192 + })
+12
src/stores/counter.ts
··· 1 + import { ref, computed } from "vue"; 2 + import { defineStore } from "pinia"; 3 + 4 + export const useCounterStore = defineStore("counter", () => { 5 + const count = ref(0); 6 + const doubleCount = computed(() => count.value * 2); 7 + function increment() { 8 + count.value++; 9 + } 10 + 11 + return { count, doubleCount, increment }; 12 + });
+49
src/stores/environment.ts
··· 1 + import { defineStore } from "pinia"; 2 + import { ref, computed } from "vue"; 3 + 4 + export const useEnvironmentStore = defineStore("environment", () => { 5 + const windowWidth = ref(window.innerWidth); 6 + const windowHeight = ref(window.innerHeight); 7 + const _prefersReducedMotion = ref(false); 8 + const _prefersDarkScheme = ref(false); 9 + 10 + const MOBILE_BREAKPOINT = 512; 11 + 12 + const isMobile = computed(() => windowWidth.value < MOBILE_BREAKPOINT); 13 + const isDesktop = computed(() => windowWidth.value >= MOBILE_BREAKPOINT); 14 + const prefersReducedMotion = computed(() => _prefersReducedMotion.value); 15 + const prefersDarkScheme = computed(() => _prefersDarkScheme.value); 16 + 17 + function updateDimensions() { 18 + windowWidth.value = window.innerWidth; 19 + windowHeight.value = window.innerHeight; 20 + } 21 + 22 + function init() { 23 + window.addEventListener("resize", updateDimensions, { passive: true }); 24 + 25 + const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); 26 + _prefersReducedMotion.value = motionQuery.matches; 27 + motionQuery.addEventListener("change", (e) => { 28 + _prefersReducedMotion.value = e.matches; 29 + }); 30 + 31 + const colourQuery = window.matchMedia("(prefers-color-scheme: dark)"); 32 + _prefersDarkScheme.value = colourQuery.matches; 33 + colourQuery.addEventListener("change", (e) => { 34 + _prefersDarkScheme.value = e.matches; 35 + }); 36 + 37 + updateDimensions(); 38 + } 39 + 40 + return { 41 + windowWidth, 42 + windowHeight, 43 + isMobile, 44 + isDesktop, 45 + prefersReducedMotion, 46 + prefersDarkScheme, 47 + init, 48 + }; 49 + });
+296
src/stores/navigation.ts
··· 1 + import { defineStore } from "pinia"; 2 + import { ref } from "vue"; 3 + import { 4 + compileUrl, 5 + getPageByName, 6 + matchPageByPath, 7 + type PageNames, 8 + type StackRootNames, 9 + stackRoots, 10 + } from "@/router"; 11 + 12 + type TabKey = StackRootNames; 13 + type PageKey = PageNames; 14 + 15 + export type StackEntry = { 16 + id: string; 17 + page: PageKey; 18 + title?: string; 19 + props?: Record<string, unknown>; 20 + }; 21 + 22 + function generateId(page: string) { 23 + return `${page}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`; 24 + } 25 + 26 + function serializeProps(props?: Record<string, unknown>): string { 27 + if (!props || Object.keys(props).length === 0) return ""; 28 + const params = new URLSearchParams(); 29 + Object.entries(props).forEach(([key, value]) => { 30 + if (typeof value === "object") params.set(key, JSON.stringify(value)); 31 + else params.set(key, String(value)); 32 + }); 33 + return `?${params.toString()}`; 34 + } 35 + 36 + export const useNavigationStore = defineStore("navigation", () => { 37 + const isInitialized = ref(false); 38 + const activeTab = ref<TabKey>("home"); 39 + const pendingPop = ref<{ tab: TabKey; count: number } | null>(null); 40 + 41 + const stacks = ref<Record<TabKey, StackEntry[]>>( 42 + stackRoots.reduce( 43 + (acc, page) => { 44 + acc[page.name as TabKey] = [ 45 + { id: generateId(page.name), page: page.name, title: page.label }, 46 + ]; 47 + return acc; 48 + }, 49 + {} as Record<TabKey, StackEntry[]>, 50 + ), 51 + ); 52 + 53 + function updateHistory( 54 + method: "push" | "replace", 55 + tab: TabKey, 56 + entry: StackEntry, 57 + ) { 58 + const pageConfig = getPageByName(entry.page); 59 + if (!pageConfig) return; 60 + 61 + const { url, remainingProps } = compileUrl( 62 + pageConfig.path, 63 + entry.props || {}, 64 + ); 65 + const fullPath = url + serializeProps(remainingProps); 66 + 67 + const state = { 68 + tab, 69 + page: entry.page, 70 + stackId: entry.id, 71 + }; 72 + 73 + if (method === "replace") window.history.replaceState(state, "", fullPath); 74 + else window.history.pushState(state, "", fullPath); 75 + } 76 + 77 + function push( 78 + page: PageKey, 79 + opts?: { 80 + tab?: TabKey; 81 + title?: string; 82 + props?: Record<string, unknown>; 83 + }, 84 + ) { 85 + const targetTab = opts?.tab ?? activeTab.value; 86 + 87 + if (targetTab !== activeTab.value) { 88 + switchTab(targetTab); 89 + } 90 + 91 + const stack = stacks.value[targetTab]; 92 + if (!stack) return; 93 + 94 + const entry: StackEntry = { 95 + id: generateId(page), 96 + page, 97 + props: opts?.props, 98 + title: opts?.title, 99 + }; 100 + 101 + stack.push(entry); 102 + updateHistory("push", targetTab, entry); 103 + 104 + return entry; 105 + } 106 + 107 + function switchTab(tab: PageNames) { 108 + if (tab === activeTab.value) return; 109 + 110 + const targetTab = tab as TabKey; 111 + const stack = stacks.value[targetTab]; 112 + 113 + if (!stack || stack.length === 0) return; 114 + 115 + activeTab.value = targetTab; 116 + 117 + const topEntry = stack[stack.length - 1]; 118 + if (!topEntry) return; 119 + updateHistory("push", targetTab, topEntry); 120 + } 121 + 122 + function resetTab(tab: TabKey) { 123 + const stack = stacks.value[tab]; 124 + if (!stack || stack.length <= 1) return; 125 + 126 + const itemsToRemove = stack.length - 1; 127 + pendingPop.value = { tab, count: itemsToRemove }; 128 + 129 + const rootEntry = stack[0]; 130 + if (rootEntry) updateHistory("push", tab, rootEntry); 131 + } 132 + 133 + function pop() { 134 + window.history.back(); 135 + } 136 + 137 + function popStack() { 138 + const stack = stacks.value[activeTab.value]; 139 + if (!stack || stack.length <= 1) return; 140 + 141 + pendingPop.value = { tab: activeTab.value, count: 1 }; 142 + 143 + const previousEntry = stack[stack.length - 2]; 144 + if (!previousEntry) return; 145 + updateHistory("push", activeTab.value, previousEntry); 146 + } 147 + 148 + function popTo(index: number, tab: TabKey = activeTab.value) { 149 + const stack = stacks.value[tab]; 150 + if (!stack || index >= stack.length) return; 151 + 152 + const newStack = stack.slice(0, index + 1); 153 + stacks.value[tab] = newStack; 154 + 155 + const topEntry = newStack[newStack.length - 1]; 156 + if (!topEntry) return; 157 + updateHistory("push", tab, topEntry); 158 + } 159 + 160 + function completePop() { 161 + if (!pendingPop.value) return; 162 + 163 + const { tab, count } = pendingPop.value; 164 + const stack = stacks.value[tab]; 165 + 166 + if (stack && stack.length > count) 167 + stack.splice(stack.length - count, count); 168 + 169 + pendingPop.value = null; 170 + } 171 + 172 + async function handlePopState(event: PopStateEvent) { 173 + const state = event.state; 174 + 175 + if (!state || !state.stackId) { 176 + const { page, props } = parseUrl(); 177 + const pageConfig = getPageByName(page); 178 + if (pageConfig) { 179 + const stack = stacks.value[activeTab.value]; 180 + if (stack) { 181 + stack.push({ 182 + id: generateId(page), 183 + page: page as PageKey, 184 + props, 185 + }); 186 + 187 + const top = stack[0]; 188 + if (!top) return; 189 + updateHistory("replace", activeTab.value, top); 190 + } 191 + } 192 + return; 193 + } 194 + 195 + const { tab, stackId, page } = state; 196 + 197 + if (tab !== activeTab.value) { 198 + activeTab.value = tab; 199 + return; 200 + } 201 + 202 + // @ts-expect-error: PopStateEvent's `state` is literally just `any` which 203 + // is annoying 204 + const stack = stacks.value[tab]; 205 + // @ts-expect-error: see above 206 + const currentIndex = stack.findIndex((entry) => entry.id === stackId); 207 + 208 + if (currentIndex !== -1) { 209 + if (currentIndex < stack.length - 1) { 210 + const itemsToRemove = stack.length - 1 - currentIndex; 211 + pendingPop.value = { tab, count: itemsToRemove }; 212 + } 213 + } else { 214 + const { props } = parseUrl(); 215 + 216 + stack.push({ 217 + id: stackId, 218 + page: page, 219 + props: props, 220 + }); 221 + } 222 + } 223 + 224 + function parseUrl() { 225 + const path = window.location.pathname; 226 + const searchParams = new URLSearchParams(window.location.search); 227 + 228 + const match = matchPageByPath(path); 229 + const props: Record<string, unknown> = {}; 230 + if (match?.params) Object.assign(props, match.params); 231 + 232 + for (const [key, value] of searchParams.entries()) { 233 + try { 234 + props[key] = JSON.parse(value); 235 + } catch { 236 + props[key] = value; 237 + } 238 + } 239 + 240 + return { 241 + page: match?.page.name || "home", 242 + root: match?.page.root || false, 243 + props, 244 + }; 245 + } 246 + 247 + function init() { 248 + if (isInitialized.value) return; 249 + 250 + const { page, root, props } = parseUrl(); 251 + 252 + if (root) { 253 + activeTab.value = page as TabKey; 254 + const stack = stacks.value[activeTab.value]; 255 + if (!stack[0]) return; 256 + 257 + stack[0].props = props; 258 + updateHistory("replace", activeTab.value, stack[0]); 259 + } else { 260 + // deep links - we open these in the home tab. 261 + activeTab.value = "home"; 262 + 263 + const stack = stacks.value["home"]; 264 + const rootEntry = stack[0]; 265 + if (!rootEntry) return; 266 + 267 + const subPageEntry: StackEntry = { 268 + id: generateId(page), 269 + page: page as PageKey, 270 + props, 271 + }; 272 + 273 + stacks.value["home"] = [rootEntry, subPageEntry]; 274 + updateHistory("replace", "home", rootEntry); 275 + updateHistory("push", "home", subPageEntry); 276 + } 277 + 278 + window.addEventListener("popstate", handlePopState); 279 + isInitialized.value = true; 280 + } 281 + 282 + return { 283 + activeTab, 284 + stacks, 285 + pendingPop, 286 + init, 287 + switchTab, 288 + resetTab, 289 + push, 290 + pop, 291 + popStack, 292 + popTo, 293 + completePop, 294 + isInitialized, 295 + }; 296 + });
+302
src/stores/theme.ts
··· 1 + import { defineStore } from 'pinia' 2 + import { ref, computed, watch } from 'vue' 3 + import { useEnvironmentStore } from './environment' 4 + 5 + export interface ThemeDefinition { 6 + id: string 7 + name: string 8 + type: 'light' | 'dark' 9 + variables: Record<string, string> 10 + } 11 + 12 + const latte: ThemeDefinition = { 13 + id: 'latte', 14 + name: 'Latte', 15 + type: 'light', 16 + variables: { 17 + rosewater: '10.8 58.824% 66.667%', 18 + flamingo: '0 59.763% 66.863%', 19 + pink: '316.034 73.418% 69.02%', 20 + mauve: '266.044 85.047% 58.039%', 21 + red: '347.077 86.667% 44.118%', 22 + maroon: '354.783 76.303% 58.627%', 23 + peach: '21.975 99.184% 51.961%', 24 + yellow: '34.948 76.984% 49.412%', 25 + green: '109.231 57.635% 39.804%', 26 + teal: '183.231 73.864% 34.51%', 27 + sky: '197.067 96.567% 45.686%', 28 + sapphire: '188.859 69.953% 41.765%', 29 + blue: '219.907 91.489% 53.922%', 30 + lavender: '230.935 97.203% 71.961%', 31 + text: '233.793 16.022% 35.49%', 32 + subtext1: '233.333 12.796% 41.373%', 33 + subtext0: '232.8 10.373% 47.255%', 34 + overlay2: '232.174 9.623% 53.137%', 35 + overlay1: '231.429 10.048% 59.02%', 36 + overlay0: '228 11.236% 65.098%', 37 + surface2: '226.667 12.162% 70.98%', 38 + surface1: '225 13.559% 76.863%', 39 + surface0: '222.857 15.909% 82.745%', 40 + base: '220 23.077% 94.902%', 41 + mantle: '220 21.951% 91.961%', 42 + crust: '220 20.69% 88.627%', 43 + }, 44 + } 45 + 46 + const frappe: ThemeDefinition = { 47 + id: 'frappe', 48 + name: 'Frappé', 49 + type: 'dark', 50 + variables: { 51 + rosewater: '10.286 57.377% 88.039%', 52 + flamingo: '0 58.537% 83.922%', 53 + pink: '316 73.171% 83.922%', 54 + mauve: '276.667 59.016% 76.078%', 55 + red: '358.812 67.785% 70.784%', 56 + maroon: '357.778 65.854% 75.882%', 57 + peach: '20.331 79.085% 70%', 58 + yellow: '39.529 62.044% 73.137%', 59 + green: '95.833 43.902% 67.843%', 60 + teal: '171.549 39.227% 64.51%', 61 + sky: '189.091 47.826% 72.941%', 62 + sapphire: '198.621 55.414% 69.216%', 63 + blue: '221.633 74.242% 74.118%', 64 + lavender: '238.909 66.265% 83.725%', 65 + text: '227.234 70.149% 86.863%', 66 + subtext1: '226.667 43.689% 79.804%', 67 + subtext0: '228.293 29.496% 72.745%', 68 + overlay2: '227.692 22.286% 65.686%', 69 + overlay1: '226.667 16.981% 58.431%', 70 + overlay0: '229.091 13.36% 51.569%', 71 + surface2: '228 13.274% 44.314%', 72 + surface1: '227.143 14.737% 37.255%', 73 + surface0: '230 15.584% 30.196%', 74 + base: '229.091 18.644% 23.137%', 75 + mantle: '230.526 18.812% 19.804%', 76 + crust: '229.412 19.54% 17.059%', 77 + }, 78 + } 79 + 80 + const macchiato: ThemeDefinition = { 81 + id: 'macchiato', 82 + name: 'Macchiato', 83 + type: 'dark', 84 + variables: { 85 + rosewater: '10 57.692% 89.804%', 86 + flamingo: '0 58.333% 85.882%', 87 + pink: '316.071 73.684% 85.098%', 88 + mauve: '266.512 82.692% 79.608%', 89 + red: '351.176 73.913% 72.941%', 90 + maroon: '355.059 71.429% 76.667%', 91 + peach: '21.356 85.507% 72.941%', 92 + yellow: '40.253 69.912% 77.843%', 93 + green: '105.217 48.252% 71.961%', 94 + teal: '171.081 46.835% 69.02%', 95 + sky: '188.78 59.42% 72.941%', 96 + sapphire: '198.641 65.605% 69.216%', 97 + blue: '220.189 82.813% 74.902%', 98 + lavender: '234.462 82.278% 84.51%', 99 + text: '227.442 68.254% 87.647%', 100 + subtext1: '228 39.216% 80%', 101 + subtext0: '227.368 26.761% 72.157%', 102 + overlay2: '228.333 20% 64.706%', 103 + overlay1: '227.647 15.455% 56.863%', 104 + overlay0: '230.323 12.351% 49.216%', 105 + surface2: '229.655 13.744% 41.373%', 106 + surface1: '231.111 15.607% 33.922%', 107 + surface0: '230.4 18.797% 26.078%', 108 + base: '231.818 23.404% 18.431%', 109 + mantle: '233.333 23.077% 15.294%', 110 + crust: '235.714 22.581% 12.157%', 111 + }, 112 + } 113 + 114 + const mocha: ThemeDefinition = { 115 + id: 'mocha', 116 + name: 'Mocha', 117 + type: 'dark', 118 + variables: { 119 + rosewater: '9.6 55.556% 91.176%', 120 + flamingo: '0 58.73% 87.647%', 121 + pink: '316.471 71.831% 86.078%', 122 + mauve: '267.407 83.505% 80.98%', 123 + red: '343.269 81.25% 74.902%', 124 + maroon: '350.4 65.217% 77.451%', 125 + peach: '22.957 92% 75.49%', 126 + yellow: '41.351 86.047% 83.137%', 127 + green: '115.455 54.098% 76.078%', 128 + teal: '170 57.353% 73.333%', 129 + sky: '189.184 71.014% 72.941%', 130 + sapphire: '198.5 75.949% 69.02%', 131 + blue: '217.168 91.87% 75.882%', 132 + lavender: '231.892 97.368% 85.098%', 133 + text: '226.154 63.934% 88.039%', 134 + subtext1: '226.667 35.294% 80%', 135 + subtext0: '227.647 23.611% 71.765%', 136 + overlay2: '228.387 16.757% 63.725%', 137 + overlay1: '229.655 12.775% 55.49%', 138 + overlay0: '230.769 10.744% 47.451%', 139 + surface2: '232.5 12% 39.216%', 140 + surface1: '234.286 13.208% 31.176%', 141 + surface0: '236.842 16.239% 22.941%', 142 + base: '240 21.053% 14.902%', 143 + mantle: '240 21.311% 11.961%', 144 + crust: '240 22.727% 8.627%', 145 + }, 146 + } 147 + 148 + export const AccentColours = [ 149 + 'rosewater', 150 + 'flamingo', 151 + 'pink', 152 + 'mauve', 153 + 'red', 154 + 'maroon', 155 + 'peach', 156 + 'yellow', 157 + 'green', 158 + 'teal', 159 + 'sky', 160 + 'sapphire', 161 + 'blue', 162 + 'lavender', 163 + ] as const 164 + export type AccentColour = (typeof AccentColours)[number] 165 + export const themes = [latte, frappe, macchiato, mocha] 166 + 167 + const STORAGE_KEYS = { 168 + FOLLOW_SYSTEM: 'scilla-theme-follow', 169 + PREFERRED_LIGHT: 'scilla-theme-light', 170 + PREFERRED_DARK: 'scilla-theme-dark', 171 + CURRENT_MODE: 'scilla-theme-mode', 172 + ACCENT_COLOUR: 'scilla-theme-accent', 173 + } 174 + 175 + export const useThemeStore = defineStore('theme', () => { 176 + const env = useEnvironmentStore() 177 + 178 + const followSystem = ref(true) 179 + const preferredLight = ref<string>('scilla-light') 180 + const preferredDark = ref<string>('scilla-dark') 181 + const currentMode = ref<'light' | 'dark'>('dark') 182 + const preferredAccent = ref<AccentColour>('mauve') 183 + 184 + const activeTheme = computed(() => { 185 + let targetId: string 186 + 187 + if (followSystem.value) { 188 + targetId = env.prefersDarkScheme ? preferredDark.value : preferredLight.value 189 + } else { 190 + targetId = currentMode.value === 'dark' ? preferredDark.value : preferredLight.value 191 + } 192 + 193 + return themes.find((t) => t.id === targetId) || mocha 194 + }) 195 + 196 + function setFollowSystem(val: boolean) { 197 + followSystem.value = val 198 + } 199 + 200 + function setPreferredLight(themeId: string) { 201 + if (themes.find((t) => t.id === themeId && t.type === 'light')) { 202 + preferredLight.value = themeId 203 + currentMode.value = 'light' 204 + } 205 + } 206 + 207 + function setPreferredDark(themeId: string) { 208 + if (themes.find((t) => t.id === themeId && t.type === 'dark')) { 209 + preferredDark.value = themeId 210 + currentMode.value = 'dark' 211 + } 212 + } 213 + 214 + function setAccent(colour: AccentColour) { 215 + if (AccentColours.includes(colour)) { 216 + preferredAccent.value = colour 217 + } 218 + } 219 + 220 + function applyTheme() { 221 + const root = document.documentElement 222 + const theme = activeTheme.value 223 + const accentKey = preferredAccent.value 224 + 225 + Object.entries(theme.variables).forEach(([key, value]) => { 226 + root.style.setProperty(`--${key}`, value) 227 + }) 228 + 229 + const accentValue = theme.variables[accentKey] 230 + if (accentValue) root.style.setProperty('--accent', accentValue) 231 + 232 + root.setAttribute('data-theme', theme.id) 233 + 234 + const metaThemeColor = document.querySelector('meta[name="theme-colour"]') 235 + if (metaThemeColor) { 236 + metaThemeColor.setAttribute('content', `hsl(${theme.variables.mantle})`) 237 + } 238 + } 239 + 240 + function init() { 241 + const storedFollow = localStorage.getItem(STORAGE_KEYS.FOLLOW_SYSTEM) 242 + if (storedFollow !== null) { 243 + followSystem.value = storedFollow === 'true' 244 + } 245 + 246 + const storedLight = localStorage.getItem(STORAGE_KEYS.PREFERRED_LIGHT) 247 + if (storedLight && themes.some((t) => t.id === storedLight)) { 248 + preferredLight.value = storedLight 249 + } 250 + 251 + const storedDark = localStorage.getItem(STORAGE_KEYS.PREFERRED_DARK) 252 + if (storedDark && themes.some((t) => t.id === storedDark)) { 253 + preferredDark.value = storedDark 254 + } 255 + 256 + const storedMode = localStorage.getItem(STORAGE_KEYS.CURRENT_MODE) 257 + if (storedMode === 'light' || storedMode === 'dark') { 258 + currentMode.value = storedMode 259 + } else { 260 + currentMode.value = env.prefersDarkScheme ? 'dark' : 'light' 261 + } 262 + 263 + const storedAccent = localStorage.getItem(STORAGE_KEYS.ACCENT_COLOUR) as AccentColour 264 + if (storedAccent && AccentColours.includes(storedAccent)) { 265 + preferredAccent.value = storedAccent 266 + } 267 + 268 + watch(followSystem, (val) => { 269 + localStorage.setItem(STORAGE_KEYS.FOLLOW_SYSTEM, String(val)) 270 + if (!val) { 271 + currentMode.value = env.prefersDarkScheme ? 'dark' : 'light' 272 + } 273 + }) 274 + watch(preferredLight, (val) => localStorage.setItem(STORAGE_KEYS.PREFERRED_LIGHT, val)) 275 + watch(preferredDark, (val) => localStorage.setItem(STORAGE_KEYS.PREFERRED_DARK, val)) 276 + watch(currentMode, (val) => localStorage.setItem(STORAGE_KEYS.CURRENT_MODE, val)) 277 + watch(preferredAccent, (val) => localStorage.setItem(STORAGE_KEYS.ACCENT_COLOUR, val)) 278 + 279 + watch( 280 + [activeTheme, preferredAccent, () => env.prefersDarkScheme], 281 + () => { 282 + applyTheme() 283 + }, 284 + { immediate: true }, 285 + ) 286 + } 287 + 288 + return { 289 + themes, 290 + AccentColours, 291 + followSystem, 292 + preferredLight, 293 + preferredDark, 294 + preferredAccent, 295 + activeTheme, 296 + setFollowSystem, 297 + setPreferredLight, 298 + setPreferredDark, 299 + setAccent, 300 + init, 301 + } 302 + })
+1
src/types/atproto.ts
··· 1 + export type DidString = `did:${string}:${string}`
+262
src/views/Auth/LoginPage.vue
··· 1 + <script setup lang="ts"> 2 + import { ref, watch } from 'vue' 3 + import { 4 + IconArrowForwardRounded, 5 + IconOpenInNewRounded, 6 + IconAlternateEmailRounded, 7 + IconCloseRounded, 8 + IconProgressActivity, 9 + } from '@iconify-prerendered/vue-material-symbols' 10 + import { simpleFetchHandler, Client, ok } from '@atcute/client' 11 + 12 + import { useAuthStore } from '@/stores/auth' 13 + import PageLayout from '@/components/Navigation/PageLayout.vue' 14 + import Button from '@/components/UI/BaseButton.vue' 15 + import Modal from '@/components/UI/BaseModal.vue' 16 + import TextInput from '@/components/UI/TextInput.vue' 17 + import ListItem from '@/components/UI/ListItem.vue' 18 + 19 + const auth = useAuthStore() 20 + 21 + const handler = simpleFetchHandler({ service: 'https://public.api.bsky.app' }) 22 + const client = new Client({ handler }) 23 + 24 + const profilePicture = ref<string | null>(null) 25 + const handle = ref('') 26 + const showCreateAccount = ref(false) 27 + const loading = ref(false) 28 + const hasError = ref(false) 29 + 30 + const handleSubmit = async () => { 31 + if (!handle.value) return 32 + await auth.login(handle.value) 33 + } 34 + const pdsSubmit = async (pds: string) => { 35 + await auth.login(pds) 36 + } 37 + 38 + let debounceTimer: ReturnType<typeof setTimeout> 39 + 40 + watch(handle, (newHandle) => { 41 + clearTimeout(debounceTimer) 42 + hasError.value = false 43 + loading.value = true 44 + 45 + if (!newHandle) { 46 + profilePicture.value = null 47 + loading.value = false 48 + return 49 + } 50 + 51 + debounceTimer = setTimeout(async () => { 52 + try { 53 + const cleanHandle = newHandle.startsWith('@') ? newHandle.slice(1) : newHandle 54 + const data = ok( 55 + await client.get('app.bsky.actor.getProfile', { 56 + params: { actor: cleanHandle }, 57 + }), 58 + ) 59 + 60 + profilePicture.value = data.avatar || null 61 + hasError.value = false 62 + } catch (error) { 63 + profilePicture.value = null 64 + hasError.value = true 65 + console.error('Error fetching profile:', error) 66 + } finally { 67 + loading.value = false 68 + } 69 + }, 500) 70 + }) 71 + 72 + const providerList: Array<{ name: string; subtitle?: string; url: string }> = [ 73 + { name: 'Bluesky PBC', subtitle: 'bsky.social', url: 'https://bsky.social/' }, 74 + { name: 'selfhosted.social', url: 'https://selfhosted.social/' }, 75 + { 76 + name: 'Tophhie Social', 77 + subtitle: 'pds.tophhie.cloud', 78 + url: 'https://pds.tophhie.cloud', 79 + }, 80 + { name: 'Blacksky', subtitle: 'A PDS for the black community.', url: 'https://blacksky.app/' }, 81 + { name: 'Spark', subtitle: 'pds.sprk.so', url: 'https://pds.sprk.so' }, 82 + ] 83 + </script> 84 + 85 + <template> 86 + <PageLayout title="Login"> 87 + <div class="login-view"> 88 + <div class="header"> 89 + <h1 class="title">Sign in</h1> 90 + <p class="subtitle">Enter your AT Protocol handle to continue.</p> 91 + </div> 92 + 93 + <form @submit.prevent="handleSubmit" class="form-stack"> 94 + <TextInput 95 + v-model="handle" 96 + placeholder="awesome.cat" 97 + :error="auth.error || undefined" 98 + @keydown.enter="handleSubmit" 99 + autofocus 100 + > 101 + <template #prefix> 102 + <div v-if="loading" class="text-input-prefix spin"> 103 + <IconProgressActivity /> 104 + </div> 105 + <div v-else-if="profilePicture" class="input-avatar"> 106 + <img :src="profilePicture" alt="Avatar" /> 107 + </div> 108 + <div v-else class="text-input-prefix"> 109 + <IconCloseRounded v-if="hasError" style="color: hsl(var(--danger))" /> 110 + <IconAlternateEmailRounded v-else /> 111 + </div> 112 + </template> 113 + </TextInput> 114 + 115 + <div class="actions"> 116 + <Button type="button" variant="subtle-alt" @click="showCreateAccount = true"> 117 + Create account 118 + </Button> 119 + <Button type="submit" variant="primary" :loading="auth.isLoading" :disabled="!handle"> 120 + Next <IconArrowForwardRounded /> 121 + </Button> 122 + </div> 123 + </form> 124 + </div> 125 + 126 + <Modal v-model:open="showCreateAccount" title="Create Account"> 127 + <div class="create-account-content"> 128 + <p class="modal-text"> 129 + To use Scilla, you need to create an Atmosphere account. Here are some open providers 130 + where you can register. 131 + </p> 132 + 133 + <div class="provider-list"> 134 + <ListItem 135 + v-for="provider in providerList" 136 + :key="provider.name" 137 + :title="provider.name" 138 + :subtitle="provider.subtitle" 139 + @click="pdsSubmit(provider.url)" 140 + clickable 141 + :disabled="auth.isLoading" 142 + > 143 + <template #end><IconOpenInNewRounded /></template> 144 + </ListItem> 145 + </div> 146 + 147 + <p class="modal-subtext"> 148 + Make sure to to read the provider's terms of service and privacy policy before creating an 149 + account. 150 + </p> 151 + </div> 152 + <template #footer> 153 + <Button variant="ghost" @click="showCreateAccount = false">Close</Button> 154 + </template> 155 + </Modal> 156 + </PageLayout> 157 + </template> 158 + 159 + <style scoped lang="scss"> 160 + .login-view { 161 + max-width: 400px; 162 + width: 100%; 163 + display: flex; 164 + flex-direction: column; 165 + gap: 1rem; 166 + } 167 + 168 + .header { 169 + display: flex; 170 + flex-direction: column; 171 + gap: 0.5rem; 172 + 173 + .title { 174 + font-size: 2rem; 175 + font-weight: 800; 176 + color: hsl(var(--text)); 177 + letter-spacing: -0.02em; 178 + } 179 + 180 + .subtitle { 181 + font-size: 1rem; 182 + color: hsl(var(--subtext0)); 183 + line-height: 1.5; 184 + } 185 + } 186 + 187 + .form-stack { 188 + display: flex; 189 + flex-direction: column; 190 + gap: 1rem; 191 + 192 + .actions { 193 + display: flex; 194 + justify-content: flex-end; 195 + gap: 1rem; 196 + } 197 + 198 + .text-input-prefix { 199 + display: flex; 200 + align-items: center; 201 + 202 + &.spin { 203 + animation: spin 1s linear infinite; 204 + color: hsl(var(--subtext0)); 205 + 206 + @keyframes spin { 207 + from { 208 + transform: rotate(0deg); 209 + } 210 + to { 211 + transform: rotate(360deg); 212 + } 213 + } 214 + } 215 + } 216 + 217 + .input-avatar { 218 + display: flex; 219 + align-items: center; 220 + justify-content: center; 221 + padding-left: 0.5rem; 222 + padding-right: 0.5rem; 223 + height: 100%; 224 + 225 + img { 226 + width: 2rem; 227 + height: 2rem; 228 + 229 + border-radius: 50%; 230 + object-fit: cover; 231 + background-color: hsl(var(--surface0)); 232 + border: 1px solid hsl(var(--surface2)); 233 + } 234 + } 235 + } 236 + 237 + .create-account-content { 238 + display: flex; 239 + flex-direction: column; 240 + gap: 1rem; 241 + padding-top: 0.5rem; 242 + 243 + .modal-text { 244 + color: hsl(var(--text)); 245 + line-height: 1.5; 246 + } 247 + 248 + .modal-subtext { 249 + font-size: 0.875rem; 250 + color: hsl(var(--subtext0)); 251 + line-height: 1.4; 252 + } 253 + 254 + .provider-list { 255 + display: flex; 256 + flex-direction: column; 257 + border: 1px solid hsla(var(--surface2) / 0.5); 258 + border-radius: var(--radius-lg); 259 + overflow: hidden; 260 + } 261 + } 262 + </style>
+368
src/views/Auth/OAuthCallback.vue
··· 1 + <script setup lang="ts"> 2 + import { onMounted, ref, computed } from 'vue' 3 + import { useAuthStore } from '@/stores/auth' 4 + import SVG from '@/components/UI/SVG.vue' 5 + import ScillaLogo from '@/assets/icons/scilla.svg?raw' 6 + import { IconArrowForwardRounded } from '@iconify-prerendered/vue-material-symbols' 7 + import Button from '@/components/UI/BaseButton.vue' 8 + const emit = defineEmits<{ 9 + (e: 'complete'): void 10 + }>() 11 + 12 + const auth = useAuthStore() 13 + 14 + type ViewState = 'loading' | 'success' | 'error' 15 + const state = ref<ViewState>('loading') 16 + const loadingMessage = ref('Connecting...') 17 + const profile = ref<any>(null) 18 + const errorMsg = ref('') 19 + const isExiting = ref(false) 20 + 21 + const loadingBar = ref<HTMLElement | null>(null) 22 + 23 + const displayName = computed(() => { 24 + if (!profile.value) return '' 25 + return profile.value.displayName || profile.value.handle 26 + }) 27 + 28 + const bannerUrl = computed(() => profile.value?.banner) 29 + const avatarUrl = computed(() => profile.value?.avatar) 30 + 31 + const proceed = async () => { 32 + isExiting.value = true 33 + await new Promise((r) => setTimeout(r, 600)) 34 + emit('complete') 35 + } 36 + 37 + onMounted(async () => { 38 + try { 39 + const style = loadingBar.value?.style 40 + 41 + loadingMessage.value = 'verifying... ' 42 + style?.setProperty('--scale', 1 / 3) 43 + 44 + const success = await auth.handleCallback() 45 + if (!success && !auth.isAuthenticated) throw new Error('Auth failed') 46 + 47 + loadingMessage.value = 'finding you...' 48 + style?.setProperty('--scale', 2 / 3) 49 + 50 + const rpc = auth.getRpc() 51 + const { data } = await rpc.get('app.bsky.actor.getProfile', { 52 + params: { actor: auth.session?.info.sub }, 53 + }) 54 + 55 + if (data.banner) { 56 + const img = new Image() 57 + img.src = data.banner 58 + } 59 + 60 + profile.value = data 61 + loadingMessage.value = "nothing's happening this is just a spacer" 62 + style?.setProperty('--scale', '1') 63 + 64 + await new Promise((r) => setTimeout(r, 750)) 65 + state.value = 'success' 66 + } catch (e: any) { 67 + console.error(e) 68 + state.value = 'error' 69 + errorMsg.value = e.message || 'Something went wrong' 70 + setTimeout(() => (window.location.href = '/login'), 3000) 71 + } 72 + }) 73 + </script> 74 + 75 + <template> 76 + <div class="callback-screen" :class="{ 'is-exiting': isExiting }"> 77 + <div 78 + class="bg-layer" 79 + :class="{ 'has-image': !!bannerUrl && state === 'success' }" 80 + :style="{ 81 + backgroundImage: bannerUrl && state === 'success' ? `url(${bannerUrl})` : undefined, 82 + '--opacity': state === 'success' ? 1 : 0, 83 + }" 84 + > 85 + <div class="bg-overlay"></div> 86 + </div> 87 + 88 + <Transition name="fade-up" mode="out-in"> 89 + <div v-if="state === 'loading'" class="content-layer loading-layout"> 90 + <div class="logo-mark"> 91 + <SVG :icon="ScillaLogo" /> 92 + </div> 93 + <div class="loading-bar-track"> 94 + <div class="loading-bar-fill" ref="loadingBar"></div> 95 + </div> 96 + <p class="loading-text">{{ loadingMessage }}</p> 97 + </div> 98 + 99 + <div v-else-if="state === 'success'" class="content-layer success-layout"> 100 + <div class="avatar-hero"> 101 + <img v-if="avatarUrl" :src="avatarUrl" alt="Avatar" /> 102 + <div v-else class="avatar-fallback"><SVG :icon="ScillaLogo" /></div> 103 + </div> 104 + 105 + <div class="text-hero"> 106 + <h1 class="greeting">Hi,</h1> 107 + <h1 class="name">{{ displayName }}!</h1> 108 + </div> 109 + 110 + <Button class="start-btn" @click="proceed" size="lg" variant="primary"> 111 + <span>meow!</span> 112 + <IconArrowForwardRounded /> 113 + </Button> 114 + </div> 115 + 116 + <div v-else-if="state === 'error'" class="content-layer error-layout"> 117 + <h1 class="error-title">Connection Failed</h1> 118 + <p class="error-desc">{{ errorMsg }}</p> 119 + </div> 120 + </Transition> 121 + </div> 122 + </template> 123 + 124 + <style scoped lang="scss"> 125 + .callback-screen { 126 + position: fixed; 127 + inset: 0; 128 + background-color: hsl(var(--base)); 129 + display: flex; 130 + align-items: center; 131 + justify-content: center; 132 + overflow: hidden; 133 + z-index: 9999; 134 + perspective: 1000px; 135 + } 136 + 137 + .bg-layer { 138 + position: absolute; 139 + inset: -20px; 140 + background-color: hsl(var(--base)); 141 + background-size: cover; 142 + background-position: center; 143 + opacity: var(--opacity, 0); 144 + 145 + transition: all 1s cubic-bezier(0.25, 1, 0.5, 1); 146 + will-change: transform, opacity, filter; 147 + 148 + &::before { 149 + content: ''; 150 + position: absolute; 151 + inset: 0; 152 + background: radial-gradient(circle at 50% 120%, hsla(var(--accent) / 0.15), transparent 70%); 153 + opacity: 1; 154 + transition: opacity 1s ease; 155 + } 156 + 157 + &.has-image { 158 + filter: blur(40px) saturate(1.2); 159 + transform: scale(1.1); 160 + &::before { 161 + opacity: 1; 162 + } 163 + } 164 + } 165 + 166 + .bg-overlay { 167 + position: absolute; 168 + inset: 0; 169 + background: linear-gradient(to bottom, hsla(var(--base) / 0.8), hsla(var(--base) / 0.95)); 170 + } 171 + 172 + .content-layer { 173 + position: relative; 174 + z-index: 10; 175 + width: 100%; 176 + max-width: 600px; 177 + padding: 2rem; 178 + display: flex; 179 + flex-direction: column; 180 + align-items: center; 181 + text-align: center; 182 + 183 + will-change: transform, opacity, filter; 184 + transition: all 0.6s cubic-bezier(0.6, 0, 0.4, 1); 185 + } 186 + 187 + .loading-layout { 188 + gap: 1rem; 189 + 190 + .logo-mark { 191 + width: 3rem; 192 + height: 3rem; 193 + color: hsl(var(--text)); 194 + opacity: 0.2; 195 + } 196 + 197 + .loading-bar-track { 198 + width: 200px; 199 + height: 2px; 200 + background: hsla(var(--surface2) / 0.5); 201 + border-radius: 2px; 202 + overflow: hidden; 203 + } 204 + 205 + .loading-bar-fill { 206 + --scale: 0; 207 + height: 100%; 208 + width: 100%; 209 + background: hsl(var(--accent)); 210 + transform-origin: left; 211 + transform: scaleX(var(--scale)); 212 + } 213 + 214 + .loading-text { 215 + font-size: 0.875rem; 216 + color: hsl(var(--subtext0)); 217 + letter-spacing: 0.05em; 218 + text-transform: uppercase; 219 + font-weight: 600; 220 + } 221 + } 222 + 223 + .success-layout { 224 + gap: 1.5rem; 225 + 226 + .avatar-hero { 227 + width: 8rem; 228 + height: 8rem; 229 + border-radius: 50%; 230 + overflow: hidden; 231 + box-shadow: 0 20px 50px -10px hsla(var(--accent) / 0.3); 232 + animation: scaleIn 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); 233 + 234 + img { 235 + width: 100%; 236 + height: 100%; 237 + object-fit: cover; 238 + } 239 + 240 + .avatar-fallback { 241 + width: 100%; 242 + height: 100%; 243 + background: hsl(var(--surface1)); 244 + display: flex; 245 + align-items: center; 246 + justify-content: center; 247 + color: hsl(var(--accent)); 248 + padding: 2rem; 249 + } 250 + } 251 + 252 + .text-hero { 253 + display: flex; 254 + flex-direction: column; 255 + gap: 0.25rem; 256 + 257 + .greeting { 258 + font-size: 1.5rem; 259 + font-weight: 500; 260 + color: hsl(var(--subtext0)); 261 + animation: slideUp 0.6s ease 0.1s backwards; 262 + } 263 + 264 + .name { 265 + font-size: 3rem; 266 + font-weight: 800; 267 + color: hsl(var(--accent)); 268 + animation: slideUp 0.6s ease 0.2s backwards; 269 + } 270 + } 271 + 272 + .start-btn { 273 + background: hsl(var(--text)); 274 + color: hsl(var(--base)); 275 + border: none; 276 + padding: 1rem 2rem; 277 + border-radius: 99px; 278 + font-size: 1rem; 279 + font-weight: 600; 280 + cursor: pointer; 281 + display: flex; 282 + align-items: center; 283 + gap: 0.75rem; 284 + animation: slideUp 0.6s ease 0.3s backwards; 285 + 286 + &:hover { 287 + transform: translateY(-2px); 288 + box-shadow: 0 10px 20px -5px hsla(var(--accent) / 0.3); 289 + } 290 + &:active { 291 + transform: translateY(0); 292 + box-shadow: 0 5px 20px -5px hsla(var(--accent) / 0.3); 293 + } 294 + } 295 + } 296 + 297 + .error-layout { 298 + .error-title { 299 + color: hsl(var(--red)); 300 + font-size: 1.5rem; 301 + font-weight: 700; 302 + } 303 + .error-desc { 304 + color: hsl(var(--subtext0)); 305 + } 306 + } 307 + 308 + .is-exiting { 309 + .content-layer { 310 + transform: scale(1.2); 311 + opacity: 0; 312 + filter: blur(15px); 313 + } 314 + 315 + .bg-layer { 316 + transform: scale(1.15); 317 + opacity: 0; 318 + transition-duration: 0.8s; 319 + } 320 + } 321 + 322 + @keyframes progress { 323 + 0% { 324 + transform: scaleX(0) translateX(0%); 325 + } 326 + 50% { 327 + transform: scaleX(0.5) translateX(0%); 328 + } 329 + 100% { 330 + transform: scaleX(1) translateX(0%); 331 + } 332 + } 333 + 334 + @keyframes scaleIn { 335 + from { 336 + transform: scale(0.5); 337 + opacity: 0; 338 + } 339 + to { 340 + transform: scale(1); 341 + opacity: 1; 342 + } 343 + } 344 + 345 + @keyframes slideUp { 346 + from { 347 + transform: translateY(20px); 348 + opacity: 0; 349 + } 350 + to { 351 + transform: translateY(0); 352 + opacity: 1; 353 + } 354 + } 355 + 356 + .fade-up-enter-active, 357 + .fade-up-leave-active { 358 + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); 359 + } 360 + .fade-up-enter-from { 361 + opacity: 0; 362 + transform: translateY(20px); 363 + } 364 + .fade-up-leave-to { 365 + opacity: 0; 366 + transform: translateY(-20px); 367 + } 368 + </style>
+11
src/views/Root/HomeView.vue
··· 1 + <script lang="ts" setup> 2 + import PageLayout from "@/components/Navigation/PageLayout.vue"; 3 + import AppLink from "@/components/Navigation/AppLink.vue"; 4 + </script> 5 + 6 + <template> 7 + <PageLayout title="Home"> 8 + <h1>Home</h1> 9 + <AppLink name="subpage">Go to SubPage</AppLink> 10 + </PageLayout> 11 + </template>
+17
src/views/Root/SearchView.vue
··· 1 + <script lang="ts" setup> 2 + import PageLayout from "@/components/Navigation/PageLayout.vue"; 3 + import AppLink from "@/components/Navigation/AppLink.vue"; 4 + </script> 5 + 6 + <template> 7 + <PageLayout title="Search"> 8 + <h1>search placeholder!</h1> 9 + 10 + <div style="height: 1500px;">meow meow meow</div> 11 + 12 + <AppLink name="home">Go to SubPage</AppLink> 13 + <AppLink name="search" :params="{ userId: 123, mode: 'edit' }"> 14 + Go to SubPage with params 15 + </AppLink> 16 + </PageLayout> 17 + </template>
+447
src/views/SettingsPage.vue
··· 1 + <script lang="ts" setup> 2 + import { ref, computed } from 'vue' 3 + import { useThemeStore, AccentColours } from '@/stores/theme' 4 + import { useEnvironmentStore } from '@/stores/environment' 5 + import { useNavigationStore } from '@/stores/navigation' 6 + import { useAuthStore } from '@/stores/auth' 7 + 8 + import AppLink from '@/components/Navigation/AppLink.vue' 9 + import PageLayout from '@/components/Navigation/PageLayout.vue' 10 + import ListGroup from '@/components/UI/ListGroup.vue' 11 + import ListItem from '@/components/UI/ListItem.vue' 12 + import ToggleSwitch from '@/components/UI/ToggleSwitch.vue' 13 + import Modal from '@/components/UI/BaseModal.vue' 14 + import Button from '@/components/UI/BaseButton.vue' 15 + 16 + import { 17 + IconPaletteOutline, 18 + IconInfoOutlineRounded, 19 + IconCheckCircleRounded, 20 + IconMoonStarsRounded, 21 + IconChevronRightRounded, 22 + IconOpenInBrowserRounded, 23 + IconCheckRounded, 24 + IconLoginRounded, 25 + IconLogoutRounded, 26 + } from '@iconify-prerendered/vue-material-symbols' 27 + import tangledLogo from '@/assets/icons/tangled.svg?raw' 28 + 29 + const appVersion = __APP_VERSION__ 30 + 31 + const themeStore = useThemeStore() 32 + const env = useEnvironmentStore() 33 + const nav = useNavigationStore() 34 + const auth = useAuthStore() 35 + 36 + const showThemeModal = ref(false) 37 + const showAccentModal = ref(false) 38 + 39 + const currentThemeLabel = computed(() => { 40 + if (themeStore.followSystem) return 'System Default' 41 + return themeStore.activeTheme.name 42 + }) 43 + 44 + const currentAccentLabel = computed(() => { 45 + const accent = themeStore.preferredAccent 46 + return accent.charAt(0).toUpperCase() + accent.slice(1) 47 + }) 48 + 49 + const availableThemes = computed(() => themeStore.themes) 50 + 51 + const selectTheme = (themeId: string, type: 'light' | 'dark') => { 52 + if (type === 'dark') themeStore.setPreferredDark(themeId) 53 + else themeStore.setPreferredLight(themeId) 54 + } 55 + </script> 56 + 57 + <template> 58 + <PageLayout title="Settings"> 59 + <ListGroup title="Appearance"> 60 + <ListItem 61 + clickable 62 + title="Follow System" 63 + @click="themeStore.followSystem = !themeStore.followSystem" 64 + tabindex="-1" 65 + > 66 + <template #start><IconMoonStarsRounded /></template> 67 + <template #end><ToggleSwitch v-model="themeStore.followSystem" /></template> 68 + </ListItem> 69 + 70 + <ListItem 71 + title="Theme Preference" 72 + :subtitle="currentThemeLabel" 73 + clickable 74 + @click="showThemeModal = true" 75 + > 76 + <template #start><IconPaletteOutline /></template> 77 + <template #end> 78 + <div 79 + class="mini-palette" 80 + :style="{ background: `hsl(${themeStore.activeTheme.variables.base})` }" 81 + > 82 + <div 83 + class="dot" 84 + :style="{ background: `hsl(${themeStore.activeTheme.variables.blue})` }" 85 + ></div> 86 + <div 87 + class="dot" 88 + :style="{ background: `hsl(${themeStore.activeTheme.variables.pink})` }" 89 + ></div> 90 + </div> 91 + <IconChevronRightRounded class="chevron" /> 92 + </template> 93 + </ListItem> 94 + 95 + <ListItem 96 + title="Accent Colour" 97 + :subtitle="currentAccentLabel" 98 + clickable 99 + @click="showAccentModal = true" 100 + > 101 + <template #start> 102 + <div 103 + class="current-accent-dot" 104 + :style="{ backgroundColor: `hsl(var(--${themeStore.preferredAccent}))` }" 105 + ></div> 106 + </template> 107 + <template #end> 108 + <IconChevronRightRounded class="chevron" /> 109 + </template> 110 + </ListItem> 111 + </ListGroup> 112 + 113 + <ListGroup title="Account"> 114 + <ListItem 115 + v-if="auth.isAuthenticated" 116 + title="Sign Out" 117 + :subtitle="auth.userDid" 118 + clickable 119 + danger 120 + @click="auth.logout()" 121 + > 122 + <template #start><IconLogoutRounded /></template> 123 + </ListItem> 124 + 125 + <ListItem 126 + v-else 127 + title="Sign In" 128 + subtitle="Login to Bluesky" 129 + @click="nav.push('login')" 130 + clickable 131 + > 132 + <template #start><IconLoginRounded /></template> 133 + <template #end><IconChevronRightRounded class="chevron" /></template> 134 + </ListItem> 135 + </ListGroup> 136 + 137 + <ListGroup title="General"> 138 + <ListItem 139 + title="Source Code" 140 + href="https://tangled.org/thkt.app" 141 + target="_blank" 142 + rel="noopener" 143 + > 144 + <template #start><div style="display: flex" v-html="tangledLogo" /></template> 145 + <template #end><IconOpenInBrowserRounded class="chevron" /></template> 146 + </ListItem> 147 + <ListItem title="Version"> 148 + <template #start><IconInfoOutlineRounded /></template> 149 + <template #end 150 + ><span class="version-text">v{{ appVersion }}</span></template 151 + > 152 + </ListItem> 153 + </ListGroup> 154 + 155 + <Modal v-model:open="showThemeModal" title="Select Theme" width="640px"> 156 + <div class="theme-modal-scroll"> 157 + <div class="theme-section"> 158 + <div class="section-header">Light Mode</div> 159 + <div class="palette-grid"> 160 + <button 161 + v-for="t in availableThemes.filter((x) => x.type === 'light')" 162 + :key="t.id" 163 + class="palette-card" 164 + :class="{ active: themeStore.preferredLight === t.id }" 165 + @click="selectTheme(t.id, 'light')" 166 + :style="{ 167 + '--t-base': `hsl(${t.variables.base})`, 168 + '--t-mantle': `hsl(${t.variables.mantle})`, 169 + '--t-crust': `hsl(${t.variables.crust})`, 170 + '--t-overlay': `hsl(${t.variables.overlay})`, 171 + '--t-text': `hsl(${t.variables.text})`, 172 + '--t-surface': `hsl(${t.variables.surface0})`, 173 + '--t-blue': `hsl(${t.variables.blue})`, 174 + '--t-pink': `hsl(${t.variables.pink})`, 175 + '--t-green': `hsl(${t.variables.green})`, 176 + }" 177 + > 178 + <div class="card-bg"></div> 179 + <div class="card-content"> 180 + <span class="theme-name">{{ t.name }}</span> 181 + <div class="color-row"> 182 + <div class="color-chip" style="background-color: var(--t-blue)"></div> 183 + <div class="color-chip" style="background-color: var(--t-pink)"></div> 184 + <div class="color-chip" style="background-color: var(--t-green)"></div> 185 + <div class="color-chip" style="background-color: var(--t-text)"></div> 186 + </div> 187 + </div> 188 + <div class="active-ring" v-if="themeStore.preferredLight === t.id"> 189 + <IconCheckCircleRounded /> 190 + </div> 191 + </button> 192 + </div> 193 + </div> 194 + 195 + <div class="theme-section"> 196 + <div class="section-header">Dark Mode</div> 197 + <div class="palette-grid"> 198 + <button 199 + v-for="t in availableThemes.filter((x) => x.type === 'dark')" 200 + :key="t.id" 201 + class="palette-card" 202 + :class="{ active: themeStore.preferredDark === t.id }" 203 + @click="selectTheme(t.id, 'dark')" 204 + :style="{ 205 + '--t-base': `hsl(${t.variables.base})`, 206 + '--t-mantle': `hsl(${t.variables.mantle})`, 207 + '--t-crust': `hsl(${t.variables.crust})`, 208 + '--t-text': `hsl(${t.variables.text})`, 209 + '--t-surface': `hsl(${t.variables.surface0})`, 210 + '--t-blue': `hsl(${t.variables.blue})`, 211 + '--t-pink': `hsl(${t.variables.pink})`, 212 + '--t-green': `hsl(${t.variables.green})`, 213 + }" 214 + > 215 + <div class="card-bg"></div> 216 + <div class="card-content"> 217 + <span class="theme-name">{{ t.name }}</span> 218 + <div class="color-row"> 219 + <div class="color-chip" style="background-color: var(--t-blue)"></div> 220 + <div class="color-chip" style="background-color: var(--t-pink)"></div> 221 + <div class="color-chip" style="background-color: var(--t-green)"></div> 222 + <div class="color-chip" style="background-color: var(--t-text)"></div> 223 + </div> 224 + </div> 225 + <div class="active-ring" v-if="themeStore.preferredDark === t.id"> 226 + <IconCheckCircleRounded /> 227 + </div> 228 + </button> 229 + </div> 230 + </div> 231 + </div> 232 + <template #footer> 233 + <Button variant="ghost" @click="showThemeModal = false">Done</Button> 234 + </template> 235 + </Modal> 236 + 237 + <Modal v-model:open="showAccentModal" title="Accent Colour" width="640px"> 238 + <div class="accent-grid"> 239 + <button 240 + v-for="colour in AccentColours" 241 + :key="colour" 242 + class="accent-btn" 243 + :class="{ active: themeStore.preferredAccent === colour }" 244 + @click="themeStore.setAccent(colour)" 245 + :aria-label="`Select ${colour} accent`" 246 + > 247 + <div class="accent-preview" :style="{ backgroundColor: `hsl(var(--${colour}))` }"> 248 + <IconCheckRounded class="check-icon" /> 249 + </div> 250 + <span class="accent-name">{{ colour }}</span> 251 + </button> 252 + </div> 253 + <template #footer> 254 + <Button variant="primary" @click="showAccentModal = false">Done</Button> 255 + </template> 256 + </Modal> 257 + </PageLayout> 258 + </template> 259 + 260 + <style scoped lang="scss"> 261 + .version-text { 262 + font-family: monospace; 263 + color: hsl(var(--subtext0)); 264 + font-size: 0.875rem; 265 + } 266 + 267 + .chevron { 268 + font-size: 1.25rem; 269 + opacity: 0.4; 270 + } 271 + 272 + .current-accent-dot { 273 + width: 1.5rem; 274 + height: 1.5rem; 275 + border-radius: 50%; 276 + border: 2px solid hsla(var(--surface2) / 0.5); 277 + } 278 + 279 + .accent-grid { 280 + display: grid; 281 + grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); 282 + gap: 1rem; 283 + padding-bottom: 1rem; 284 + 285 + .accent-btn { 286 + display: flex; 287 + flex-direction: column; 288 + align-items: center; 289 + gap: 0.5rem; 290 + background: none; 291 + border: none; 292 + cursor: pointer; 293 + padding: 0.5rem; 294 + border-radius: var(--radius-md); 295 + 296 + svg { 297 + opacity: 0; 298 + } 299 + 300 + &:hover { 301 + background-color: hsl(var(--surface0)); 302 + } 303 + 304 + .accent-preview { 305 + width: 3rem; 306 + height: 3rem; 307 + border-radius: 50%; 308 + display: flex; 309 + align-items: center; 310 + justify-content: center; 311 + color: hsl(var(--base)); 312 + 313 + .check-icon { 314 + font-size: 1.75rem; 315 + } 316 + } 317 + 318 + .accent-name { 319 + font-size: 0.75rem; 320 + color: hsl(var(--subtext0)); 321 + text-transform: capitalize; 322 + font-weight: 700; 323 + } 324 + 325 + &.active { 326 + svg { 327 + opacity: 1; 328 + } 329 + .accent-name { 330 + color: hsl(var(--text)); 331 + } 332 + } 333 + } 334 + } 335 + 336 + .theme-modal-scroll { 337 + display: flex; 338 + flex-direction: column; 339 + gap: 2rem; 340 + padding-bottom: 1rem; 341 + 342 + .mini-palette { 343 + display: flex; 344 + gap: 4px; 345 + padding: 4px; 346 + border-radius: 99px; 347 + border: 1px solid hsla(var(--text) / 0.1); 348 + 349 + .dot { 350 + width: 0.75rem; 351 + height: 0.75rem; 352 + border-radius: 50%; 353 + } 354 + } 355 + 356 + .section-header { 357 + font-size: 0.75rem; 358 + text-transform: uppercase; 359 + letter-spacing: 0.05em; 360 + font-weight: 700; 361 + color: hsl(var(--subtext0)); 362 + margin-bottom: 0.75rem; 363 + padding-left: 0.25rem; 364 + } 365 + 366 + .palette-grid { 367 + display: grid; 368 + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); 369 + gap: 1rem; 370 + } 371 + 372 + .palette-card { 373 + position: relative; 374 + display: flex; 375 + flex-direction: column; 376 + border: none; 377 + background: transparent; 378 + padding: 0; 379 + cursor: pointer; 380 + border-radius: var(--radius-md); 381 + overflow: hidden; 382 + text-align: left; 383 + 384 + .card-bg { 385 + position: absolute; 386 + inset: 0; 387 + background-color: var(--t-base); 388 + border: 1px solid hsla(var(--text) / 0.1); 389 + border-radius: var(--radius-md); 390 + } 391 + 392 + .card-content { 393 + position: relative; 394 + z-index: 1; 395 + padding: 1rem; 396 + display: flex; 397 + flex-direction: column; 398 + gap: 0.75rem; 399 + } 400 + 401 + .theme-name { 402 + font-weight: 700; 403 + font-size: 0.9rem; 404 + color: var(--t-text); 405 + } 406 + 407 + .color-row { 408 + display: flex; 409 + gap: 0.5rem; 410 + } 411 + 412 + .color-chip { 413 + width: 1rem; 414 + height: 1rem; 415 + border-radius: 50%; 416 + } 417 + 418 + .active-ring { 419 + position: absolute; 420 + top: 0.5rem; 421 + right: 0.5rem; 422 + z-index: 2; 423 + color: var(--t-blue); 424 + background: var(--t-base); 425 + border-radius: 50%; 426 + width: 1.25rem; 427 + height: 1.25rem; 428 + display: flex; 429 + align-items: center; 430 + justify-content: center; 431 + box-shadow: 0 2px 4px hsla(var(--crust) / 0.1); 432 + } 433 + 434 + &:hover .card-bg { 435 + background-color: var(--t-mantle); 436 + } 437 + &:active .card-bg { 438 + background-color: var(--t-crust); 439 + } 440 + 441 + &.active .card-bg { 442 + border-color: var(--t-blue); 443 + box-shadow: 0 0 0 2px var(--t-blue); 444 + } 445 + } 446 + } 447 + </style>
+13
src/views/SubPage.vue
··· 1 + <script lang="ts" setup> 2 + import PageLayout from "@/components/Navigation/PageLayout.vue"; 3 + import AppLink from "@/components/Navigation/AppLink.vue"; 4 + 5 + const id = Math.random().toString(36).substring(2, 9); 6 + </script> 7 + 8 + <template> 9 + <PageLayout :title="`Subpage ${id}`"> 10 + <h1>Subpage {{ id }}</h1> 11 + <AppLink name="search">Go to Search</AppLink> 12 + </PageLayout> 13 + </template>
+26
src/views/UserProfile.vue
··· 1 + <script lang="ts" setup> 2 + import PageLayout from "@/components/Navigation/PageLayout.vue"; 3 + import AppLink from "@/components/Navigation/AppLink.vue"; 4 + 5 + defineProps<{ 6 + id: string; 7 + otherProp?: string; 8 + }>(); 9 + </script> 10 + 11 + <template> 12 + <PageLayout :title="`user ${id}`"> 13 + <p>user id: {{ id }}</p> 14 + <AppLink name="home">back home</AppLink> 15 + 16 + <AppLink 17 + name="user-profile" 18 + :params="{ id: String(Math.floor(Math.random() * 1000)) }" 19 + > 20 + go to view random user 21 + </AppLink> 22 + </PageLayout> 23 + </template> 24 + 25 + <style scoped> 26 + </style>
+12
tsconfig.app.json
··· 1 + { 2 + "extends": "@vue/tsconfig/tsconfig.dom.json", 3 + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 + "exclude": ["src/**/__tests__/*"], 5 + "compilerOptions": { 6 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 7 + "types": ["@atcute/bluesky", "@atcute/atproto"], 8 + "paths": { 9 + "@/*": ["./src/*"] 10 + } 11 + } 12 + }
+11
tsconfig.json
··· 1 + { 2 + "files": [], 3 + "references": [ 4 + { 5 + "path": "./tsconfig.node.json" 6 + }, 7 + { 8 + "path": "./tsconfig.app.json" 9 + } 10 + ] 11 + }
+19
tsconfig.node.json
··· 1 + { 2 + "extends": "@tsconfig/node24/tsconfig.json", 3 + "include": [ 4 + "vite.config.*", 5 + "vitest.config.*", 6 + "cypress.config.*", 7 + "nightwatch.conf.*", 8 + "playwright.config.*", 9 + "eslint.config.*" 10 + ], 11 + "compilerOptions": { 12 + "noEmit": true, 13 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 14 + 15 + "module": "ESNext", 16 + "moduleResolution": "Bundler", 17 + "types": ["node"] 18 + } 19 + }
+42
vite.config.ts
··· 1 + import { fileURLToPath, URL } from 'node:url' 2 + 3 + import { defineConfig } from 'vite' 4 + import vue from '@vitejs/plugin-vue' 5 + import vueDevTools from 'vite-plugin-vue-devtools' 6 + 7 + import packageJson from './package.json' 8 + import metadata from './public/oauth-client-metadata.json' with { type: 'json' } 9 + 10 + const SERVER_HOST = '127.0.0.1' 11 + const SERVER_PORT = 5173 12 + 13 + export default defineConfig({ 14 + plugins: [ 15 + vue(), 16 + vueDevTools(), 17 + { 18 + name: 'oauth-config', 19 + config(_conf, { command }) { 20 + if (command === 'build') { 21 + process.env.VITE_OAUTH_CLIENT_ID = metadata.client_id 22 + process.env.VITE_OAUTH_REDIRECT_URI = metadata.redirect_uris[0] 23 + } else { 24 + const redirectUri = `http://${SERVER_HOST}:${SERVER_PORT}${new URL(metadata.redirect_uris[0]).pathname}` 25 + process.env.VITE_OAUTH_CLIENT_ID = 26 + `http://localhost?redirect_uri=${encodeURIComponent(redirectUri)}` + 27 + `&scope=${encodeURIComponent(metadata.scope)}` 28 + process.env.VITE_OAUTH_REDIRECT_URI = redirectUri 29 + } 30 + process.env.VITE_OAUTH_SCOPE = metadata.scope 31 + }, 32 + }, 33 + ], 34 + define: { 35 + __APP_VERSION__: JSON.stringify(packageJson.version), 36 + }, 37 + resolve: { 38 + alias: { 39 + '@': fileURLToPath(new URL('./src', import.meta.url)), 40 + }, 41 + }, 42 + })