Chess on the ATmosphere checkmate.blue
chess
18
fork

Configure Feed

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

Initial sloplementation.

+4863
+23
.gitignore
··· 1 + node_modules 2 + 3 + # Output 4 + .output 5 + .vercel 6 + .netlify 7 + .wrangler 8 + /.svelte-kit 9 + /build 10 + 11 + # OS 12 + .DS_Store 13 + Thumbs.db 14 + 15 + # Env 16 + .env 17 + .env.* 18 + !.env.example 19 + !.env.test 20 + 21 + # Vite 22 + vite.config.js.timestamp-* 23 + vite.config.ts.timestamp-*
+1
.npmrc
··· 1 + engine-strict=true
+6
.vscode/extensions.json
··· 1 + { 2 + "recommendations": [ 3 + "svelte.svelte-vscode", 4 + "bradlc.vscode-tailwindcss" 5 + ] 6 + }
+5
.vscode/settings.json
··· 1 + { 2 + "files.associations": { 3 + "*.css": "tailwindcss" 4 + } 5 + }
+75
CLAUDE.md
··· 1 + ## Project Configuration 2 + 3 + - **Language**: TypeScript 4 + - **Package Manager**: npm 5 + - **Add-ons**: tailwindcss, sveltekit-adapter 6 + 7 + --- 8 + 9 + # checkmate.blue 10 + 11 + Federated chess platform built on AT Protocol. Players authenticate with Bluesky identity, play real-time 1v1 chess with game state stored as atproto records, move updates via Jetstream. No application database, no server-side game logic. 12 + 13 + See `SPEC.md` for full technical specification. 14 + 15 + ## Stack 16 + 17 + - **Framework:** SvelteKit (static adapter) 18 + - **Chess logic:** chess.js (client-side validation, PGN generation) 19 + - **Board UI:** @lichess-org/chessground (Lichess BSD-licensed board) 20 + - **Auth:** @atproto/oauth-client-browser (sessions in IndexedDB) 21 + - **AT Protocol:** @atproto/api (read/write records to PDS) 22 + - **Real-time:** Jetstream WebSocket (opponent move events) 23 + - **Queries:** Constellation (game discovery), Slingshot (handle/DID resolution) 24 + - **Styling:** Tailwind CSS 25 + - **Domain:** checkmate.blue 26 + 27 + ## Key Architecture Decisions 28 + 29 + - No app server or database. SvelteKit app deploys as a static site. 30 + - Each player writes moves to their OWN repo (atproto permissions are per-user). Both players maintain their own game record with full PGN. 31 + - White's game record is the "primary" record; Black's record has a `parentGameUri` pointing back to it. The game URL always uses White's DID and rkey. 32 + - On load, both records are read and the longer PGN is used (since each record is one move behind 50% of the time). 33 + - PGN is the source of truth for game state. 34 + - Move validation is client-side only (acceptable for PoC). 35 + - Jetstream delivers real-time move updates; falls back to polling on disconnect. 36 + 37 + ## Lexicons 38 + 39 + Namespace: `blue.checkmate.*` 40 + 41 + - `blue.checkmate.game` -- chess game record (key: tid) 42 + - `blue.checkmate.challenge` -- challenge to play (key: tid) 43 + 44 + Lexicon definitions live in `lexicons/`. 45 + 46 + ## Commands 47 + 48 + ```bash 49 + npm run dev # Start dev server 50 + npm run build # Build for production 51 + npm run preview # Preview production build 52 + npm run check # Type check 53 + ``` 54 + 55 + ## Development Notes 56 + 57 + - Vite dev server binds to `127.0.0.1` (not localhost) -- required for atproto OAuth loopback client. 58 + - In dev mode, OAuth uses `clientMetadata: undefined` (loopback client). In prod, it uses the full metadata object. 59 + - OAuth client-metadata.json is prerendered at `/oauth/client-metadata.json` via a `+server.ts` route. 60 + - SPA mode: `ssr = false` in root `+layout.ts`, static adapter with `fallback: 'index.html'`. 61 + - Svelte 5 runes: state files use `.svelte.ts` extension and `$state` instead of traditional stores. 62 + - Chessground requires 3 CSS imports (base, brown, cburnett) -- all imported in `layout.css`. 63 + 64 + ## Game Record Pairing 65 + 66 + When Black joins a game, they create their own `blue.checkmate.game` record on their PDS with `parentGameUri` set to White's game AT-URI. This pairing is how each side finds the other's record: 67 + 68 + - **White finds Black's record:** `findGameRecordByParent(agent, blackDid, parentUri)` searches Black's PDS for a record whose `parentGameUri` matches. 69 + - **Black finds their own record:** Same function against their own PDS. 70 + 71 + When White discovers Black via Jetstream (`waitForOpponent`), White persists `{ black: blackDid, status: 'active' }` to their own record so the pairing survives page reload. 72 + 73 + ## Specs 74 + 75 + Feature specs live in `specs/` and follow the `/spec` workflow. Treat specs as source of truth for implementation. If something isn't covered by a spec, ask rather than guessing.
+72
README.md
··· 1 1 # checkmate.blue 2 + 3 + Federated chess on [AT Protocol](https://atproto.com). Play real-time 1v1 chess using your Bluesky identity -- no accounts to create, no server to trust. Game state lives in each player's personal data repository. 4 + 5 + **https://checkmate.blue** 6 + 7 + ## How it works 8 + 9 + 1. Sign in with your Bluesky account (via AT Protocol OAuth) 10 + 2. Challenge another player by handle or accept an open challenge 11 + 3. Play chess -- moves are written to your PDS as `blue.checkmate.game` records 12 + 4. Your opponent's moves arrive in real-time via [Jetstream](https://docs.bsky.app/blog/jetstream) 13 + 14 + There is no application server. The entire app is a static site that talks directly to each player's PDS. Each player maintains their own copy of the game record with the full PGN. The board reconciles state by reading both records and using the one with more moves. 15 + 16 + ## Stack 17 + 18 + | Layer | Technology | 19 + |-------|-----------| 20 + | Framework | [SvelteKit](https://svelte.dev) (static adapter) | 21 + | Chess logic | [chess.js](https://github.com/jhlywa/chess.js) | 22 + | Board UI | [@lichess-org/chessground](https://github.com/lichess-org/chessground) | 23 + | Auth | [@atproto/oauth-client-browser](https://github.com/bluesky-social/atproto) | 24 + | Data | [AT Protocol](https://atproto.com) records on each player's PDS | 25 + | Real-time | [Jetstream](https://docs.bsky.app/blog/jetstream) WebSocket | 26 + | Styling | [Tailwind CSS](https://tailwindcss.com) | 27 + 28 + ## Development 29 + 30 + ```bash 31 + npm install 32 + npm run dev # Dev server on 127.0.0.1:5173 33 + npm run build # Production build 34 + npm run preview # Preview production build 35 + npm run check # Type check 36 + ``` 37 + 38 + The dev server binds to `127.0.0.1` (not `localhost`) because atproto OAuth loopback clients require an IP address. 39 + 40 + ## Lexicons 41 + 42 + Custom AT Protocol lexicons under the `blue.checkmate` namespace: 43 + 44 + - **`blue.checkmate.game`** -- A chess game record containing PGN, player DIDs, status, and optional time control data. 45 + - **`blue.checkmate.challenge`** -- A challenge record linking to a game, used to invite a specific opponent. 46 + 47 + Definitions live in [`lexicons/`](lexicons/). 48 + 49 + ## Architecture 50 + 51 + ``` 52 + Player A (White) Player B (Black) 53 + +-----------------+ +-----------------+ 54 + | Browser | | Browser | 55 + | - chess.js | | - chess.js | 56 + | - chessground | | - chessground | 57 + +--------+--------+ +--------+--------+ 58 + | | 59 + write moves write moves 60 + | | 61 + v v 62 + +--------+--------+ +--------+--------+ 63 + | Player A's PDS | | Player B's PDS | 64 + | game record |<--- Jetstream ------->| game record | 65 + | (primary) | (real-time) | (parentGameUri) | 66 + +-----------------+ +-----------------+ 67 + ``` 68 + 69 + Each player writes moves only to their own PDS (atproto enforces per-user write permissions). Both records contain the full PGN. On load, the app reads both records and uses the longer PGN, since each record is one move behind on the opponent's turns. 70 + 71 + ## License 72 + 73 + MIT
+15
lexicons/README.md
··· 1 + # Lexicons 2 + 3 + AT Protocol lexicon definitions for checkmate.blue. Namespace: `blue.checkmate.*` 4 + 5 + ## Records 6 + 7 + ### `blue.checkmate.game` 8 + 9 + Chess game record. Both players maintain their own copy -- White's is the primary, Black's links back via `parentGameUri`. PGN is the source of truth for game state; updated via `putRecord` on each move. 10 + 11 + `timeControl` and `moveTimes` are defined but not yet implemented. All games are currently untimed. 12 + 13 + ### `blue.checkmate.challenge` 14 + 15 + Challenge to play. Can target a specific opponent (by DID) or be left open. Once accepted, `gameUri` is set and `status` moves to `accepted`.
+36
lexicons/blue.checkmate.challenge.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "blue.checkmate.challenge", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "description": "A challenge to play chess on checkmate.blue", 9 + "record": { 10 + "type": "object", 11 + "required": ["createdAt", "status"], 12 + "properties": { 13 + "createdAt": { 14 + "type": "string", 15 + "format": "datetime" 16 + }, 17 + "opponent": { 18 + "type": "string", 19 + "format": "did", 20 + "description": "DID of the specific opponent, or omit for open challenge" 21 + }, 22 + "gameUri": { 23 + "type": "string", 24 + "format": "at-uri", 25 + "description": "AT URI of the game record once accepted" 26 + }, 27 + "status": { 28 + "type": "string", 29 + "knownValues": ["open", "accepted", "expired", "cancelled"], 30 + "description": "Current challenge status" 31 + } 32 + } 33 + } 34 + } 35 + } 36 + }
+95
lexicons/blue.checkmate.game.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "blue.checkmate.game", 4 + "defs": { 5 + "timeControl": { 6 + "type": "object", 7 + "description": "Time control settings for the game", 8 + "required": ["type"], 9 + "properties": { 10 + "type": { 11 + "type": "string", 12 + "knownValues": ["untimed", "correspondence", "clock"], 13 + "description": "Type of time control" 14 + }, 15 + "initialSeconds": { 16 + "type": "integer", 17 + "description": "Starting time per player in seconds" 18 + }, 19 + "incrementSeconds": { 20 + "type": "integer", 21 + "description": "Seconds added after each move (Fischer increment)" 22 + }, 23 + "moveTimeLimitSeconds": { 24 + "type": "integer", 25 + "description": "Max seconds per move for correspondence games" 26 + } 27 + } 28 + }, 29 + "main": { 30 + "type": "record", 31 + "key": "tid", 32 + "description": "A chess game on checkmate.blue", 33 + "record": { 34 + "type": "object", 35 + "required": ["pgn", "createdAt", "status"], 36 + "properties": { 37 + "pgn": { 38 + "type": "string", 39 + "maxLength": 100000, 40 + "description": "PGN of the game including headers and moves so far" 41 + }, 42 + "createdAt": { 43 + "type": "string", 44 + "format": "datetime" 45 + }, 46 + "white": { 47 + "type": "string", 48 + "format": "did", 49 + "description": "DID of the white player" 50 + }, 51 + "black": { 52 + "type": "string", 53 + "format": "did", 54 + "description": "DID of the black player" 55 + }, 56 + "status": { 57 + "type": "string", 58 + "knownValues": ["waiting", "active", "completed", "abandoned"], 59 + "description": "Current game status" 60 + }, 61 + "result": { 62 + "type": "string", 63 + "knownValues": ["1-0", "0-1", "1/2-1/2"], 64 + "description": "Game result in standard PGN notation" 65 + }, 66 + "resultReason": { 67 + "type": "string", 68 + "knownValues": ["checkmate", "resignation", "draw_agreement", "stalemate", "insufficient", "repetition", "fifty_moves"], 69 + "description": "Reason for the game result" 70 + }, 71 + "parentGameUri": { 72 + "type": "string", 73 + "format": "at-uri", 74 + "description": "AT URI of the white player's game record, set on the black player's copy" 75 + }, 76 + "drawOffered": { 77 + "type": "boolean", 78 + "description": "Whether a draw has been offered by the last moving player" 79 + }, 80 + "timeControl": { 81 + "type": "ref", 82 + "ref": "#timeControl" 83 + }, 84 + "moveTimes": { 85 + "type": "array", 86 + "items": { 87 + "type": "integer" 88 + }, 89 + "description": "Seconds elapsed for each half-move, in ply order (white move 1, black move 1, white move 2, ...)" 90 + } 91 + } 92 + } 93 + } 94 + } 95 + }
+2629
package-lock.json
··· 1 + { 2 + "name": "checkmate.blue", 3 + "version": "0.0.1", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "checkmate.blue", 9 + "version": "0.0.1", 10 + "dependencies": { 11 + "@atproto/api": "^0.19.5", 12 + "@atproto/oauth-client-browser": "^0.3.41", 13 + "@lichess-org/chessground": "^10.1.1", 14 + "chess.js": "^1.4.0" 15 + }, 16 + "devDependencies": { 17 + "@sveltejs/adapter-static": "^3.0.10", 18 + "@sveltejs/kit": "^2.50.2", 19 + "@sveltejs/vite-plugin-svelte": "^6.2.4", 20 + "@tailwindcss/vite": "^4.1.18", 21 + "svelte": "^5.54.0", 22 + "svelte-check": "^4.4.2", 23 + "tailwindcss": "^4.1.18", 24 + "typescript": "^5.9.3", 25 + "vite": "^7.3.1" 26 + } 27 + }, 28 + "node_modules/@atproto-labs/did-resolver": { 29 + "version": "0.2.6", 30 + "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.6.tgz", 31 + "integrity": "sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg==", 32 + "license": "MIT", 33 + "dependencies": { 34 + "@atproto-labs/fetch": "0.2.3", 35 + "@atproto-labs/pipe": "0.1.1", 36 + "@atproto-labs/simple-store": "0.3.0", 37 + "@atproto-labs/simple-store-memory": "0.1.4", 38 + "@atproto/did": "0.3.0", 39 + "zod": "^3.23.8" 40 + } 41 + }, 42 + "node_modules/@atproto-labs/fetch": { 43 + "version": "0.2.3", 44 + "resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.2.3.tgz", 45 + "integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==", 46 + "license": "MIT", 47 + "dependencies": { 48 + "@atproto-labs/pipe": "0.1.1" 49 + } 50 + }, 51 + "node_modules/@atproto-labs/handle-resolver": { 52 + "version": "0.3.6", 53 + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.6.tgz", 54 + "integrity": "sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA==", 55 + "license": "MIT", 56 + "dependencies": { 57 + "@atproto-labs/simple-store": "0.3.0", 58 + "@atproto-labs/simple-store-memory": "0.1.4", 59 + "@atproto/did": "0.3.0", 60 + "zod": "^3.23.8" 61 + } 62 + }, 63 + "node_modules/@atproto-labs/identity-resolver": { 64 + "version": "0.3.6", 65 + "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.3.6.tgz", 66 + "integrity": "sha512-qoWqBDRobln0NR8L8dQjSp79E0chGkBhibEgxQa2f9WD+JbJdjQ0YvwwO5yeQn05pJoJmAwmI2wyJ45zjU7aWg==", 67 + "license": "MIT", 68 + "dependencies": { 69 + "@atproto-labs/did-resolver": "0.2.6", 70 + "@atproto-labs/handle-resolver": "0.3.6" 71 + } 72 + }, 73 + "node_modules/@atproto-labs/pipe": { 74 + "version": "0.1.1", 75 + "resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.1.tgz", 76 + "integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==", 77 + "license": "MIT" 78 + }, 79 + "node_modules/@atproto-labs/simple-store": { 80 + "version": "0.3.0", 81 + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz", 82 + "integrity": "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==", 83 + "license": "MIT" 84 + }, 85 + "node_modules/@atproto-labs/simple-store-memory": { 86 + "version": "0.1.4", 87 + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz", 88 + "integrity": "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==", 89 + "license": "MIT", 90 + "dependencies": { 91 + "@atproto-labs/simple-store": "0.3.0", 92 + "lru-cache": "^10.2.0" 93 + } 94 + }, 95 + "node_modules/@atproto/api": { 96 + "version": "0.19.5", 97 + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.19.5.tgz", 98 + "integrity": "sha512-u6R5TecYJDO8l8QFN09AMuJASYnUkJ4HhYE5hg4/dha/z14a+OAil2/dli/208uM5AHPFLtlnB8kIK9XU5GgQQ==", 99 + "license": "MIT", 100 + "dependencies": { 101 + "@atproto/common-web": "^0.4.19", 102 + "@atproto/lexicon": "^0.6.2", 103 + "@atproto/syntax": "^0.5.2", 104 + "@atproto/xrpc": "^0.7.7", 105 + "await-lock": "^2.2.2", 106 + "multiformats": "^9.9.0", 107 + "tlds": "^1.234.0", 108 + "zod": "^3.23.8" 109 + } 110 + }, 111 + "node_modules/@atproto/common-web": { 112 + "version": "0.4.19", 113 + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.19.tgz", 114 + "integrity": "sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw==", 115 + "license": "MIT", 116 + "dependencies": { 117 + "@atproto/lex-data": "^0.0.14", 118 + "@atproto/lex-json": "^0.0.14", 119 + "@atproto/syntax": "^0.5.1", 120 + "zod": "^3.23.8" 121 + } 122 + }, 123 + "node_modules/@atproto/did": { 124 + "version": "0.3.0", 125 + "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.3.0.tgz", 126 + "integrity": "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA==", 127 + "license": "MIT", 128 + "dependencies": { 129 + "zod": "^3.23.8" 130 + } 131 + }, 132 + "node_modules/@atproto/jwk": { 133 + "version": "0.6.0", 134 + "resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.6.0.tgz", 135 + "integrity": "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw==", 136 + "license": "MIT", 137 + "dependencies": { 138 + "multiformats": "^9.9.0", 139 + "zod": "^3.23.8" 140 + } 141 + }, 142 + "node_modules/@atproto/jwk-jose": { 143 + "version": "0.1.11", 144 + "resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz", 145 + "integrity": "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q==", 146 + "license": "MIT", 147 + "dependencies": { 148 + "@atproto/jwk": "0.6.0", 149 + "jose": "^5.2.0" 150 + } 151 + }, 152 + "node_modules/@atproto/jwk-webcrypto": { 153 + "version": "0.2.0", 154 + "resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.2.0.tgz", 155 + "integrity": "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg==", 156 + "license": "MIT", 157 + "dependencies": { 158 + "@atproto/jwk": "0.6.0", 159 + "@atproto/jwk-jose": "0.1.11", 160 + "zod": "^3.23.8" 161 + } 162 + }, 163 + "node_modules/@atproto/lex-data": { 164 + "version": "0.0.14", 165 + "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.14.tgz", 166 + "integrity": "sha512-53DUa9664SS76nGAMYopWsO10OH0AAdf7P/HSKB6Wzx3iqe6lk/K61QZnKxOG1LreYl5CfvIJU6eNf4txI6GlQ==", 167 + "license": "MIT", 168 + "dependencies": { 169 + "multiformats": "^9.9.0", 170 + "tslib": "^2.8.1", 171 + "uint8arrays": "3.0.0", 172 + "unicode-segmenter": "^0.14.0" 173 + } 174 + }, 175 + "node_modules/@atproto/lex-json": { 176 + "version": "0.0.14", 177 + "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.14.tgz", 178 + "integrity": "sha512-6lPkDKqe7teEu4WrN5q7400cvZKgYS3uwUMvzG3F9XkgVYhOwSDCtouV/nSLBbpvo3l9OP0kiigtclcNcyekww==", 179 + "license": "MIT", 180 + "dependencies": { 181 + "@atproto/lex-data": "^0.0.14", 182 + "tslib": "^2.8.1" 183 + } 184 + }, 185 + "node_modules/@atproto/lexicon": { 186 + "version": "0.6.2", 187 + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.6.2.tgz", 188 + "integrity": "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw==", 189 + "license": "MIT", 190 + "dependencies": { 191 + "@atproto/common-web": "^0.4.18", 192 + "@atproto/syntax": "^0.5.0", 193 + "iso-datestring-validator": "^2.2.2", 194 + "multiformats": "^9.9.0", 195 + "zod": "^3.23.8" 196 + } 197 + }, 198 + "node_modules/@atproto/oauth-client": { 199 + "version": "0.6.0", 200 + "resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.6.0.tgz", 201 + "integrity": "sha512-F7ZTKzFptXgyihMkd7QTdRSkrh4XqrS+qTw+V81k5Q6Bh3MB1L3ypvfSJ6v7SSUJa6XxoZYJTCahHC1e+ndE6Q==", 202 + "license": "MIT", 203 + "dependencies": { 204 + "@atproto-labs/did-resolver": "^0.2.6", 205 + "@atproto-labs/fetch": "^0.2.3", 206 + "@atproto-labs/handle-resolver": "^0.3.6", 207 + "@atproto-labs/identity-resolver": "^0.3.6", 208 + "@atproto-labs/simple-store": "^0.3.0", 209 + "@atproto-labs/simple-store-memory": "^0.1.4", 210 + "@atproto/did": "^0.3.0", 211 + "@atproto/jwk": "^0.6.0", 212 + "@atproto/oauth-types": "^0.6.3", 213 + "@atproto/xrpc": "^0.7.7", 214 + "core-js": "^3", 215 + "multiformats": "^9.9.0", 216 + "zod": "^3.23.8" 217 + } 218 + }, 219 + "node_modules/@atproto/oauth-client-browser": { 220 + "version": "0.3.41", 221 + "resolved": "https://registry.npmjs.org/@atproto/oauth-client-browser/-/oauth-client-browser-0.3.41.tgz", 222 + "integrity": "sha512-4QTm8zPgm08vl53flrVmL+MS5IOhvWWctNZmEnPbvQ2t1ISw9Q5m815m2Sszi5ULMFjOqvT7lhKB7zQUn5gq5g==", 223 + "license": "MIT", 224 + "dependencies": { 225 + "@atproto-labs/did-resolver": "^0.2.6", 226 + "@atproto-labs/handle-resolver": "^0.3.6", 227 + "@atproto-labs/simple-store": "^0.3.0", 228 + "@atproto/did": "^0.3.0", 229 + "@atproto/jwk": "^0.6.0", 230 + "@atproto/jwk-webcrypto": "^0.2.0", 231 + "@atproto/oauth-client": "^0.6.0", 232 + "@atproto/oauth-types": "^0.6.3", 233 + "core-js": "^3" 234 + } 235 + }, 236 + "node_modules/@atproto/oauth-types": { 237 + "version": "0.6.3", 238 + "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.6.3.tgz", 239 + "integrity": "sha512-jdKuoPknJuh/WjI+mYk7agSbx9mNVMbS6Dr3k1z2YMY2oRiCQjxYBuo4MLKATbxj05nMQaZRWlHRUazoAu5Cng==", 240 + "license": "MIT", 241 + "dependencies": { 242 + "@atproto/did": "^0.3.0", 243 + "@atproto/jwk": "^0.6.0", 244 + "zod": "^3.23.8" 245 + } 246 + }, 247 + "node_modules/@atproto/syntax": { 248 + "version": "0.5.2", 249 + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.5.2.tgz", 250 + "integrity": "sha512-W41szOnkppoHr0iCUrzL8gy3OD6qmDyp1UvUgmTx2oFQfgbudpz51T/gznesiCcqiUT5obfHdx4PJ+WdlEOE7Q==", 251 + "license": "MIT", 252 + "dependencies": { 253 + "tslib": "^2.8.1" 254 + } 255 + }, 256 + "node_modules/@atproto/xrpc": { 257 + "version": "0.7.7", 258 + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.7.tgz", 259 + "integrity": "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA==", 260 + "license": "MIT", 261 + "dependencies": { 262 + "@atproto/lexicon": "^0.6.0", 263 + "zod": "^3.23.8" 264 + } 265 + }, 266 + "node_modules/@esbuild/aix-ppc64": { 267 + "version": "0.27.4", 268 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", 269 + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", 270 + "cpu": [ 271 + "ppc64" 272 + ], 273 + "dev": true, 274 + "license": "MIT", 275 + "optional": true, 276 + "os": [ 277 + "aix" 278 + ], 279 + "engines": { 280 + "node": ">=18" 281 + } 282 + }, 283 + "node_modules/@esbuild/android-arm": { 284 + "version": "0.27.4", 285 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", 286 + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", 287 + "cpu": [ 288 + "arm" 289 + ], 290 + "dev": true, 291 + "license": "MIT", 292 + "optional": true, 293 + "os": [ 294 + "android" 295 + ], 296 + "engines": { 297 + "node": ">=18" 298 + } 299 + }, 300 + "node_modules/@esbuild/android-arm64": { 301 + "version": "0.27.4", 302 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", 303 + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", 304 + "cpu": [ 305 + "arm64" 306 + ], 307 + "dev": true, 308 + "license": "MIT", 309 + "optional": true, 310 + "os": [ 311 + "android" 312 + ], 313 + "engines": { 314 + "node": ">=18" 315 + } 316 + }, 317 + "node_modules/@esbuild/android-x64": { 318 + "version": "0.27.4", 319 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", 320 + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", 321 + "cpu": [ 322 + "x64" 323 + ], 324 + "dev": true, 325 + "license": "MIT", 326 + "optional": true, 327 + "os": [ 328 + "android" 329 + ], 330 + "engines": { 331 + "node": ">=18" 332 + } 333 + }, 334 + "node_modules/@esbuild/darwin-arm64": { 335 + "version": "0.27.4", 336 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", 337 + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", 338 + "cpu": [ 339 + "arm64" 340 + ], 341 + "dev": true, 342 + "license": "MIT", 343 + "optional": true, 344 + "os": [ 345 + "darwin" 346 + ], 347 + "engines": { 348 + "node": ">=18" 349 + } 350 + }, 351 + "node_modules/@esbuild/darwin-x64": { 352 + "version": "0.27.4", 353 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", 354 + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", 355 + "cpu": [ 356 + "x64" 357 + ], 358 + "dev": true, 359 + "license": "MIT", 360 + "optional": true, 361 + "os": [ 362 + "darwin" 363 + ], 364 + "engines": { 365 + "node": ">=18" 366 + } 367 + }, 368 + "node_modules/@esbuild/freebsd-arm64": { 369 + "version": "0.27.4", 370 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", 371 + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", 372 + "cpu": [ 373 + "arm64" 374 + ], 375 + "dev": true, 376 + "license": "MIT", 377 + "optional": true, 378 + "os": [ 379 + "freebsd" 380 + ], 381 + "engines": { 382 + "node": ">=18" 383 + } 384 + }, 385 + "node_modules/@esbuild/freebsd-x64": { 386 + "version": "0.27.4", 387 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", 388 + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", 389 + "cpu": [ 390 + "x64" 391 + ], 392 + "dev": true, 393 + "license": "MIT", 394 + "optional": true, 395 + "os": [ 396 + "freebsd" 397 + ], 398 + "engines": { 399 + "node": ">=18" 400 + } 401 + }, 402 + "node_modules/@esbuild/linux-arm": { 403 + "version": "0.27.4", 404 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", 405 + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", 406 + "cpu": [ 407 + "arm" 408 + ], 409 + "dev": true, 410 + "license": "MIT", 411 + "optional": true, 412 + "os": [ 413 + "linux" 414 + ], 415 + "engines": { 416 + "node": ">=18" 417 + } 418 + }, 419 + "node_modules/@esbuild/linux-arm64": { 420 + "version": "0.27.4", 421 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", 422 + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", 423 + "cpu": [ 424 + "arm64" 425 + ], 426 + "dev": true, 427 + "license": "MIT", 428 + "optional": true, 429 + "os": [ 430 + "linux" 431 + ], 432 + "engines": { 433 + "node": ">=18" 434 + } 435 + }, 436 + "node_modules/@esbuild/linux-ia32": { 437 + "version": "0.27.4", 438 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", 439 + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", 440 + "cpu": [ 441 + "ia32" 442 + ], 443 + "dev": true, 444 + "license": "MIT", 445 + "optional": true, 446 + "os": [ 447 + "linux" 448 + ], 449 + "engines": { 450 + "node": ">=18" 451 + } 452 + }, 453 + "node_modules/@esbuild/linux-loong64": { 454 + "version": "0.27.4", 455 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", 456 + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", 457 + "cpu": [ 458 + "loong64" 459 + ], 460 + "dev": true, 461 + "license": "MIT", 462 + "optional": true, 463 + "os": [ 464 + "linux" 465 + ], 466 + "engines": { 467 + "node": ">=18" 468 + } 469 + }, 470 + "node_modules/@esbuild/linux-mips64el": { 471 + "version": "0.27.4", 472 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", 473 + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", 474 + "cpu": [ 475 + "mips64el" 476 + ], 477 + "dev": true, 478 + "license": "MIT", 479 + "optional": true, 480 + "os": [ 481 + "linux" 482 + ], 483 + "engines": { 484 + "node": ">=18" 485 + } 486 + }, 487 + "node_modules/@esbuild/linux-ppc64": { 488 + "version": "0.27.4", 489 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", 490 + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", 491 + "cpu": [ 492 + "ppc64" 493 + ], 494 + "dev": true, 495 + "license": "MIT", 496 + "optional": true, 497 + "os": [ 498 + "linux" 499 + ], 500 + "engines": { 501 + "node": ">=18" 502 + } 503 + }, 504 + "node_modules/@esbuild/linux-riscv64": { 505 + "version": "0.27.4", 506 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", 507 + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", 508 + "cpu": [ 509 + "riscv64" 510 + ], 511 + "dev": true, 512 + "license": "MIT", 513 + "optional": true, 514 + "os": [ 515 + "linux" 516 + ], 517 + "engines": { 518 + "node": ">=18" 519 + } 520 + }, 521 + "node_modules/@esbuild/linux-s390x": { 522 + "version": "0.27.4", 523 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", 524 + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", 525 + "cpu": [ 526 + "s390x" 527 + ], 528 + "dev": true, 529 + "license": "MIT", 530 + "optional": true, 531 + "os": [ 532 + "linux" 533 + ], 534 + "engines": { 535 + "node": ">=18" 536 + } 537 + }, 538 + "node_modules/@esbuild/linux-x64": { 539 + "version": "0.27.4", 540 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", 541 + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", 542 + "cpu": [ 543 + "x64" 544 + ], 545 + "dev": true, 546 + "license": "MIT", 547 + "optional": true, 548 + "os": [ 549 + "linux" 550 + ], 551 + "engines": { 552 + "node": ">=18" 553 + } 554 + }, 555 + "node_modules/@esbuild/netbsd-arm64": { 556 + "version": "0.27.4", 557 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", 558 + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", 559 + "cpu": [ 560 + "arm64" 561 + ], 562 + "dev": true, 563 + "license": "MIT", 564 + "optional": true, 565 + "os": [ 566 + "netbsd" 567 + ], 568 + "engines": { 569 + "node": ">=18" 570 + } 571 + }, 572 + "node_modules/@esbuild/netbsd-x64": { 573 + "version": "0.27.4", 574 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", 575 + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", 576 + "cpu": [ 577 + "x64" 578 + ], 579 + "dev": true, 580 + "license": "MIT", 581 + "optional": true, 582 + "os": [ 583 + "netbsd" 584 + ], 585 + "engines": { 586 + "node": ">=18" 587 + } 588 + }, 589 + "node_modules/@esbuild/openbsd-arm64": { 590 + "version": "0.27.4", 591 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", 592 + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", 593 + "cpu": [ 594 + "arm64" 595 + ], 596 + "dev": true, 597 + "license": "MIT", 598 + "optional": true, 599 + "os": [ 600 + "openbsd" 601 + ], 602 + "engines": { 603 + "node": ">=18" 604 + } 605 + }, 606 + "node_modules/@esbuild/openbsd-x64": { 607 + "version": "0.27.4", 608 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", 609 + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", 610 + "cpu": [ 611 + "x64" 612 + ], 613 + "dev": true, 614 + "license": "MIT", 615 + "optional": true, 616 + "os": [ 617 + "openbsd" 618 + ], 619 + "engines": { 620 + "node": ">=18" 621 + } 622 + }, 623 + "node_modules/@esbuild/openharmony-arm64": { 624 + "version": "0.27.4", 625 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", 626 + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", 627 + "cpu": [ 628 + "arm64" 629 + ], 630 + "dev": true, 631 + "license": "MIT", 632 + "optional": true, 633 + "os": [ 634 + "openharmony" 635 + ], 636 + "engines": { 637 + "node": ">=18" 638 + } 639 + }, 640 + "node_modules/@esbuild/sunos-x64": { 641 + "version": "0.27.4", 642 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", 643 + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", 644 + "cpu": [ 645 + "x64" 646 + ], 647 + "dev": true, 648 + "license": "MIT", 649 + "optional": true, 650 + "os": [ 651 + "sunos" 652 + ], 653 + "engines": { 654 + "node": ">=18" 655 + } 656 + }, 657 + "node_modules/@esbuild/win32-arm64": { 658 + "version": "0.27.4", 659 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", 660 + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", 661 + "cpu": [ 662 + "arm64" 663 + ], 664 + "dev": true, 665 + "license": "MIT", 666 + "optional": true, 667 + "os": [ 668 + "win32" 669 + ], 670 + "engines": { 671 + "node": ">=18" 672 + } 673 + }, 674 + "node_modules/@esbuild/win32-ia32": { 675 + "version": "0.27.4", 676 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", 677 + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", 678 + "cpu": [ 679 + "ia32" 680 + ], 681 + "dev": true, 682 + "license": "MIT", 683 + "optional": true, 684 + "os": [ 685 + "win32" 686 + ], 687 + "engines": { 688 + "node": ">=18" 689 + } 690 + }, 691 + "node_modules/@esbuild/win32-x64": { 692 + "version": "0.27.4", 693 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", 694 + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", 695 + "cpu": [ 696 + "x64" 697 + ], 698 + "dev": true, 699 + "license": "MIT", 700 + "optional": true, 701 + "os": [ 702 + "win32" 703 + ], 704 + "engines": { 705 + "node": ">=18" 706 + } 707 + }, 708 + "node_modules/@jridgewell/gen-mapping": { 709 + "version": "0.3.13", 710 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 711 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 712 + "dev": true, 713 + "license": "MIT", 714 + "dependencies": { 715 + "@jridgewell/sourcemap-codec": "^1.5.0", 716 + "@jridgewell/trace-mapping": "^0.3.24" 717 + } 718 + }, 719 + "node_modules/@jridgewell/remapping": { 720 + "version": "2.3.5", 721 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 722 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 723 + "dev": true, 724 + "license": "MIT", 725 + "dependencies": { 726 + "@jridgewell/gen-mapping": "^0.3.5", 727 + "@jridgewell/trace-mapping": "^0.3.24" 728 + } 729 + }, 730 + "node_modules/@jridgewell/resolve-uri": { 731 + "version": "3.1.2", 732 + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 733 + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 734 + "dev": true, 735 + "license": "MIT", 736 + "engines": { 737 + "node": ">=6.0.0" 738 + } 739 + }, 740 + "node_modules/@jridgewell/sourcemap-codec": { 741 + "version": "1.5.5", 742 + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", 743 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", 744 + "dev": true, 745 + "license": "MIT" 746 + }, 747 + "node_modules/@jridgewell/trace-mapping": { 748 + "version": "0.3.31", 749 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 750 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 751 + "dev": true, 752 + "license": "MIT", 753 + "dependencies": { 754 + "@jridgewell/resolve-uri": "^3.1.0", 755 + "@jridgewell/sourcemap-codec": "^1.4.14" 756 + } 757 + }, 758 + "node_modules/@lichess-org/chessground": { 759 + "version": "10.1.1", 760 + "resolved": "https://registry.npmjs.org/@lichess-org/chessground/-/chessground-10.1.1.tgz", 761 + "integrity": "sha512-IBEs8+J64/zE8QB4NXxsvpjm/tHRjfQAdWwUh4xzqqN+RValgthWHemLnxsmtHFwuxvO4lHd+crp1ecgZxKVoQ==", 762 + "license": "GPL-3.0-or-later", 763 + "funding": { 764 + "url": "https://lichess.org/patron" 765 + } 766 + }, 767 + "node_modules/@polka/url": { 768 + "version": "1.0.0-next.29", 769 + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", 770 + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", 771 + "dev": true, 772 + "license": "MIT" 773 + }, 774 + "node_modules/@rollup/rollup-android-arm-eabi": { 775 + "version": "4.60.0", 776 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", 777 + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", 778 + "cpu": [ 779 + "arm" 780 + ], 781 + "dev": true, 782 + "license": "MIT", 783 + "optional": true, 784 + "os": [ 785 + "android" 786 + ] 787 + }, 788 + "node_modules/@rollup/rollup-android-arm64": { 789 + "version": "4.60.0", 790 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", 791 + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", 792 + "cpu": [ 793 + "arm64" 794 + ], 795 + "dev": true, 796 + "license": "MIT", 797 + "optional": true, 798 + "os": [ 799 + "android" 800 + ] 801 + }, 802 + "node_modules/@rollup/rollup-darwin-arm64": { 803 + "version": "4.60.0", 804 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", 805 + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", 806 + "cpu": [ 807 + "arm64" 808 + ], 809 + "dev": true, 810 + "license": "MIT", 811 + "optional": true, 812 + "os": [ 813 + "darwin" 814 + ] 815 + }, 816 + "node_modules/@rollup/rollup-darwin-x64": { 817 + "version": "4.60.0", 818 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", 819 + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", 820 + "cpu": [ 821 + "x64" 822 + ], 823 + "dev": true, 824 + "license": "MIT", 825 + "optional": true, 826 + "os": [ 827 + "darwin" 828 + ] 829 + }, 830 + "node_modules/@rollup/rollup-freebsd-arm64": { 831 + "version": "4.60.0", 832 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", 833 + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", 834 + "cpu": [ 835 + "arm64" 836 + ], 837 + "dev": true, 838 + "license": "MIT", 839 + "optional": true, 840 + "os": [ 841 + "freebsd" 842 + ] 843 + }, 844 + "node_modules/@rollup/rollup-freebsd-x64": { 845 + "version": "4.60.0", 846 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", 847 + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", 848 + "cpu": [ 849 + "x64" 850 + ], 851 + "dev": true, 852 + "license": "MIT", 853 + "optional": true, 854 + "os": [ 855 + "freebsd" 856 + ] 857 + }, 858 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 859 + "version": "4.60.0", 860 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", 861 + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", 862 + "cpu": [ 863 + "arm" 864 + ], 865 + "dev": true, 866 + "libc": [ 867 + "glibc" 868 + ], 869 + "license": "MIT", 870 + "optional": true, 871 + "os": [ 872 + "linux" 873 + ] 874 + }, 875 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 876 + "version": "4.60.0", 877 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", 878 + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", 879 + "cpu": [ 880 + "arm" 881 + ], 882 + "dev": true, 883 + "libc": [ 884 + "musl" 885 + ], 886 + "license": "MIT", 887 + "optional": true, 888 + "os": [ 889 + "linux" 890 + ] 891 + }, 892 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 893 + "version": "4.60.0", 894 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", 895 + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", 896 + "cpu": [ 897 + "arm64" 898 + ], 899 + "dev": true, 900 + "libc": [ 901 + "glibc" 902 + ], 903 + "license": "MIT", 904 + "optional": true, 905 + "os": [ 906 + "linux" 907 + ] 908 + }, 909 + "node_modules/@rollup/rollup-linux-arm64-musl": { 910 + "version": "4.60.0", 911 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", 912 + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", 913 + "cpu": [ 914 + "arm64" 915 + ], 916 + "dev": true, 917 + "libc": [ 918 + "musl" 919 + ], 920 + "license": "MIT", 921 + "optional": true, 922 + "os": [ 923 + "linux" 924 + ] 925 + }, 926 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 927 + "version": "4.60.0", 928 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", 929 + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", 930 + "cpu": [ 931 + "loong64" 932 + ], 933 + "dev": true, 934 + "libc": [ 935 + "glibc" 936 + ], 937 + "license": "MIT", 938 + "optional": true, 939 + "os": [ 940 + "linux" 941 + ] 942 + }, 943 + "node_modules/@rollup/rollup-linux-loong64-musl": { 944 + "version": "4.60.0", 945 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", 946 + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", 947 + "cpu": [ 948 + "loong64" 949 + ], 950 + "dev": true, 951 + "libc": [ 952 + "musl" 953 + ], 954 + "license": "MIT", 955 + "optional": true, 956 + "os": [ 957 + "linux" 958 + ] 959 + }, 960 + "node_modules/@rollup/rollup-linux-ppc64-gnu": { 961 + "version": "4.60.0", 962 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", 963 + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", 964 + "cpu": [ 965 + "ppc64" 966 + ], 967 + "dev": true, 968 + "libc": [ 969 + "glibc" 970 + ], 971 + "license": "MIT", 972 + "optional": true, 973 + "os": [ 974 + "linux" 975 + ] 976 + }, 977 + "node_modules/@rollup/rollup-linux-ppc64-musl": { 978 + "version": "4.60.0", 979 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", 980 + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", 981 + "cpu": [ 982 + "ppc64" 983 + ], 984 + "dev": true, 985 + "libc": [ 986 + "musl" 987 + ], 988 + "license": "MIT", 989 + "optional": true, 990 + "os": [ 991 + "linux" 992 + ] 993 + }, 994 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 995 + "version": "4.60.0", 996 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", 997 + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", 998 + "cpu": [ 999 + "riscv64" 1000 + ], 1001 + "dev": true, 1002 + "libc": [ 1003 + "glibc" 1004 + ], 1005 + "license": "MIT", 1006 + "optional": true, 1007 + "os": [ 1008 + "linux" 1009 + ] 1010 + }, 1011 + "node_modules/@rollup/rollup-linux-riscv64-musl": { 1012 + "version": "4.60.0", 1013 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", 1014 + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", 1015 + "cpu": [ 1016 + "riscv64" 1017 + ], 1018 + "dev": true, 1019 + "libc": [ 1020 + "musl" 1021 + ], 1022 + "license": "MIT", 1023 + "optional": true, 1024 + "os": [ 1025 + "linux" 1026 + ] 1027 + }, 1028 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 1029 + "version": "4.60.0", 1030 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", 1031 + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", 1032 + "cpu": [ 1033 + "s390x" 1034 + ], 1035 + "dev": true, 1036 + "libc": [ 1037 + "glibc" 1038 + ], 1039 + "license": "MIT", 1040 + "optional": true, 1041 + "os": [ 1042 + "linux" 1043 + ] 1044 + }, 1045 + "node_modules/@rollup/rollup-linux-x64-gnu": { 1046 + "version": "4.60.0", 1047 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", 1048 + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", 1049 + "cpu": [ 1050 + "x64" 1051 + ], 1052 + "dev": true, 1053 + "libc": [ 1054 + "glibc" 1055 + ], 1056 + "license": "MIT", 1057 + "optional": true, 1058 + "os": [ 1059 + "linux" 1060 + ] 1061 + }, 1062 + "node_modules/@rollup/rollup-linux-x64-musl": { 1063 + "version": "4.60.0", 1064 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", 1065 + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", 1066 + "cpu": [ 1067 + "x64" 1068 + ], 1069 + "dev": true, 1070 + "libc": [ 1071 + "musl" 1072 + ], 1073 + "license": "MIT", 1074 + "optional": true, 1075 + "os": [ 1076 + "linux" 1077 + ] 1078 + }, 1079 + "node_modules/@rollup/rollup-openbsd-x64": { 1080 + "version": "4.60.0", 1081 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", 1082 + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", 1083 + "cpu": [ 1084 + "x64" 1085 + ], 1086 + "dev": true, 1087 + "license": "MIT", 1088 + "optional": true, 1089 + "os": [ 1090 + "openbsd" 1091 + ] 1092 + }, 1093 + "node_modules/@rollup/rollup-openharmony-arm64": { 1094 + "version": "4.60.0", 1095 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", 1096 + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", 1097 + "cpu": [ 1098 + "arm64" 1099 + ], 1100 + "dev": true, 1101 + "license": "MIT", 1102 + "optional": true, 1103 + "os": [ 1104 + "openharmony" 1105 + ] 1106 + }, 1107 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 1108 + "version": "4.60.0", 1109 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", 1110 + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", 1111 + "cpu": [ 1112 + "arm64" 1113 + ], 1114 + "dev": true, 1115 + "license": "MIT", 1116 + "optional": true, 1117 + "os": [ 1118 + "win32" 1119 + ] 1120 + }, 1121 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 1122 + "version": "4.60.0", 1123 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", 1124 + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", 1125 + "cpu": [ 1126 + "ia32" 1127 + ], 1128 + "dev": true, 1129 + "license": "MIT", 1130 + "optional": true, 1131 + "os": [ 1132 + "win32" 1133 + ] 1134 + }, 1135 + "node_modules/@rollup/rollup-win32-x64-gnu": { 1136 + "version": "4.60.0", 1137 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", 1138 + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", 1139 + "cpu": [ 1140 + "x64" 1141 + ], 1142 + "dev": true, 1143 + "license": "MIT", 1144 + "optional": true, 1145 + "os": [ 1146 + "win32" 1147 + ] 1148 + }, 1149 + "node_modules/@rollup/rollup-win32-x64-msvc": { 1150 + "version": "4.60.0", 1151 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", 1152 + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", 1153 + "cpu": [ 1154 + "x64" 1155 + ], 1156 + "dev": true, 1157 + "license": "MIT", 1158 + "optional": true, 1159 + "os": [ 1160 + "win32" 1161 + ] 1162 + }, 1163 + "node_modules/@standard-schema/spec": { 1164 + "version": "1.1.0", 1165 + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", 1166 + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", 1167 + "dev": true, 1168 + "license": "MIT" 1169 + }, 1170 + "node_modules/@sveltejs/acorn-typescript": { 1171 + "version": "1.0.9", 1172 + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", 1173 + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", 1174 + "dev": true, 1175 + "license": "MIT", 1176 + "peerDependencies": { 1177 + "acorn": "^8.9.0" 1178 + } 1179 + }, 1180 + "node_modules/@sveltejs/adapter-static": { 1181 + "version": "3.0.10", 1182 + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", 1183 + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", 1184 + "dev": true, 1185 + "license": "MIT", 1186 + "peerDependencies": { 1187 + "@sveltejs/kit": "^2.0.0" 1188 + } 1189 + }, 1190 + "node_modules/@sveltejs/kit": { 1191 + "version": "2.55.0", 1192 + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.55.0.tgz", 1193 + "integrity": "sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==", 1194 + "dev": true, 1195 + "license": "MIT", 1196 + "dependencies": { 1197 + "@standard-schema/spec": "^1.0.0", 1198 + "@sveltejs/acorn-typescript": "^1.0.5", 1199 + "@types/cookie": "^0.6.0", 1200 + "acorn": "^8.14.1", 1201 + "cookie": "^0.6.0", 1202 + "devalue": "^5.6.4", 1203 + "esm-env": "^1.2.2", 1204 + "kleur": "^4.1.5", 1205 + "magic-string": "^0.30.5", 1206 + "mrmime": "^2.0.0", 1207 + "set-cookie-parser": "^3.0.0", 1208 + "sirv": "^3.0.0" 1209 + }, 1210 + "bin": { 1211 + "svelte-kit": "svelte-kit.js" 1212 + }, 1213 + "engines": { 1214 + "node": ">=18.13" 1215 + }, 1216 + "peerDependencies": { 1217 + "@opentelemetry/api": "^1.0.0", 1218 + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", 1219 + "svelte": "^4.0.0 || ^5.0.0-next.0", 1220 + "typescript": "^5.3.3", 1221 + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" 1222 + }, 1223 + "peerDependenciesMeta": { 1224 + "@opentelemetry/api": { 1225 + "optional": true 1226 + }, 1227 + "typescript": { 1228 + "optional": true 1229 + } 1230 + } 1231 + }, 1232 + "node_modules/@sveltejs/vite-plugin-svelte": { 1233 + "version": "6.2.4", 1234 + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", 1235 + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", 1236 + "dev": true, 1237 + "license": "MIT", 1238 + "dependencies": { 1239 + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", 1240 + "deepmerge": "^4.3.1", 1241 + "magic-string": "^0.30.21", 1242 + "obug": "^2.1.0", 1243 + "vitefu": "^1.1.1" 1244 + }, 1245 + "engines": { 1246 + "node": "^20.19 || ^22.12 || >=24" 1247 + }, 1248 + "peerDependencies": { 1249 + "svelte": "^5.0.0", 1250 + "vite": "^6.3.0 || ^7.0.0" 1251 + } 1252 + }, 1253 + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { 1254 + "version": "5.0.2", 1255 + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", 1256 + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", 1257 + "dev": true, 1258 + "license": "MIT", 1259 + "dependencies": { 1260 + "obug": "^2.1.0" 1261 + }, 1262 + "engines": { 1263 + "node": "^20.19 || ^22.12 || >=24" 1264 + }, 1265 + "peerDependencies": { 1266 + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", 1267 + "svelte": "^5.0.0", 1268 + "vite": "^6.3.0 || ^7.0.0" 1269 + } 1270 + }, 1271 + "node_modules/@tailwindcss/node": { 1272 + "version": "4.2.2", 1273 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", 1274 + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", 1275 + "dev": true, 1276 + "license": "MIT", 1277 + "dependencies": { 1278 + "@jridgewell/remapping": "^2.3.5", 1279 + "enhanced-resolve": "^5.19.0", 1280 + "jiti": "^2.6.1", 1281 + "lightningcss": "1.32.0", 1282 + "magic-string": "^0.30.21", 1283 + "source-map-js": "^1.2.1", 1284 + "tailwindcss": "4.2.2" 1285 + } 1286 + }, 1287 + "node_modules/@tailwindcss/oxide": { 1288 + "version": "4.2.2", 1289 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", 1290 + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", 1291 + "dev": true, 1292 + "license": "MIT", 1293 + "engines": { 1294 + "node": ">= 20" 1295 + }, 1296 + "optionalDependencies": { 1297 + "@tailwindcss/oxide-android-arm64": "4.2.2", 1298 + "@tailwindcss/oxide-darwin-arm64": "4.2.2", 1299 + "@tailwindcss/oxide-darwin-x64": "4.2.2", 1300 + "@tailwindcss/oxide-freebsd-x64": "4.2.2", 1301 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", 1302 + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", 1303 + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", 1304 + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", 1305 + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", 1306 + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", 1307 + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", 1308 + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" 1309 + } 1310 + }, 1311 + "node_modules/@tailwindcss/oxide-android-arm64": { 1312 + "version": "4.2.2", 1313 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", 1314 + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", 1315 + "cpu": [ 1316 + "arm64" 1317 + ], 1318 + "dev": true, 1319 + "license": "MIT", 1320 + "optional": true, 1321 + "os": [ 1322 + "android" 1323 + ], 1324 + "engines": { 1325 + "node": ">= 20" 1326 + } 1327 + }, 1328 + "node_modules/@tailwindcss/oxide-darwin-arm64": { 1329 + "version": "4.2.2", 1330 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", 1331 + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", 1332 + "cpu": [ 1333 + "arm64" 1334 + ], 1335 + "dev": true, 1336 + "license": "MIT", 1337 + "optional": true, 1338 + "os": [ 1339 + "darwin" 1340 + ], 1341 + "engines": { 1342 + "node": ">= 20" 1343 + } 1344 + }, 1345 + "node_modules/@tailwindcss/oxide-darwin-x64": { 1346 + "version": "4.2.2", 1347 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", 1348 + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", 1349 + "cpu": [ 1350 + "x64" 1351 + ], 1352 + "dev": true, 1353 + "license": "MIT", 1354 + "optional": true, 1355 + "os": [ 1356 + "darwin" 1357 + ], 1358 + "engines": { 1359 + "node": ">= 20" 1360 + } 1361 + }, 1362 + "node_modules/@tailwindcss/oxide-freebsd-x64": { 1363 + "version": "4.2.2", 1364 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", 1365 + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", 1366 + "cpu": [ 1367 + "x64" 1368 + ], 1369 + "dev": true, 1370 + "license": "MIT", 1371 + "optional": true, 1372 + "os": [ 1373 + "freebsd" 1374 + ], 1375 + "engines": { 1376 + "node": ">= 20" 1377 + } 1378 + }, 1379 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 1380 + "version": "4.2.2", 1381 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", 1382 + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", 1383 + "cpu": [ 1384 + "arm" 1385 + ], 1386 + "dev": true, 1387 + "license": "MIT", 1388 + "optional": true, 1389 + "os": [ 1390 + "linux" 1391 + ], 1392 + "engines": { 1393 + "node": ">= 20" 1394 + } 1395 + }, 1396 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 1397 + "version": "4.2.2", 1398 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", 1399 + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", 1400 + "cpu": [ 1401 + "arm64" 1402 + ], 1403 + "dev": true, 1404 + "libc": [ 1405 + "glibc" 1406 + ], 1407 + "license": "MIT", 1408 + "optional": true, 1409 + "os": [ 1410 + "linux" 1411 + ], 1412 + "engines": { 1413 + "node": ">= 20" 1414 + } 1415 + }, 1416 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 1417 + "version": "4.2.2", 1418 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", 1419 + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", 1420 + "cpu": [ 1421 + "arm64" 1422 + ], 1423 + "dev": true, 1424 + "libc": [ 1425 + "musl" 1426 + ], 1427 + "license": "MIT", 1428 + "optional": true, 1429 + "os": [ 1430 + "linux" 1431 + ], 1432 + "engines": { 1433 + "node": ">= 20" 1434 + } 1435 + }, 1436 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 1437 + "version": "4.2.2", 1438 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", 1439 + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", 1440 + "cpu": [ 1441 + "x64" 1442 + ], 1443 + "dev": true, 1444 + "libc": [ 1445 + "glibc" 1446 + ], 1447 + "license": "MIT", 1448 + "optional": true, 1449 + "os": [ 1450 + "linux" 1451 + ], 1452 + "engines": { 1453 + "node": ">= 20" 1454 + } 1455 + }, 1456 + "node_modules/@tailwindcss/oxide-linux-x64-musl": { 1457 + "version": "4.2.2", 1458 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", 1459 + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", 1460 + "cpu": [ 1461 + "x64" 1462 + ], 1463 + "dev": true, 1464 + "libc": [ 1465 + "musl" 1466 + ], 1467 + "license": "MIT", 1468 + "optional": true, 1469 + "os": [ 1470 + "linux" 1471 + ], 1472 + "engines": { 1473 + "node": ">= 20" 1474 + } 1475 + }, 1476 + "node_modules/@tailwindcss/oxide-wasm32-wasi": { 1477 + "version": "4.2.2", 1478 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", 1479 + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", 1480 + "bundleDependencies": [ 1481 + "@napi-rs/wasm-runtime", 1482 + "@emnapi/core", 1483 + "@emnapi/runtime", 1484 + "@tybys/wasm-util", 1485 + "@emnapi/wasi-threads", 1486 + "tslib" 1487 + ], 1488 + "cpu": [ 1489 + "wasm32" 1490 + ], 1491 + "dev": true, 1492 + "license": "MIT", 1493 + "optional": true, 1494 + "dependencies": { 1495 + "@emnapi/core": "^1.8.1", 1496 + "@emnapi/runtime": "^1.8.1", 1497 + "@emnapi/wasi-threads": "^1.1.0", 1498 + "@napi-rs/wasm-runtime": "^1.1.1", 1499 + "@tybys/wasm-util": "^0.10.1", 1500 + "tslib": "^2.8.1" 1501 + }, 1502 + "engines": { 1503 + "node": ">=14.0.0" 1504 + } 1505 + }, 1506 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 1507 + "version": "4.2.2", 1508 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", 1509 + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", 1510 + "cpu": [ 1511 + "arm64" 1512 + ], 1513 + "dev": true, 1514 + "license": "MIT", 1515 + "optional": true, 1516 + "os": [ 1517 + "win32" 1518 + ], 1519 + "engines": { 1520 + "node": ">= 20" 1521 + } 1522 + }, 1523 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 1524 + "version": "4.2.2", 1525 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", 1526 + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", 1527 + "cpu": [ 1528 + "x64" 1529 + ], 1530 + "dev": true, 1531 + "license": "MIT", 1532 + "optional": true, 1533 + "os": [ 1534 + "win32" 1535 + ], 1536 + "engines": { 1537 + "node": ">= 20" 1538 + } 1539 + }, 1540 + "node_modules/@tailwindcss/vite": { 1541 + "version": "4.2.2", 1542 + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", 1543 + "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", 1544 + "dev": true, 1545 + "license": "MIT", 1546 + "dependencies": { 1547 + "@tailwindcss/node": "4.2.2", 1548 + "@tailwindcss/oxide": "4.2.2", 1549 + "tailwindcss": "4.2.2" 1550 + }, 1551 + "peerDependencies": { 1552 + "vite": "^5.2.0 || ^6 || ^7 || ^8" 1553 + } 1554 + }, 1555 + "node_modules/@types/cookie": { 1556 + "version": "0.6.0", 1557 + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", 1558 + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", 1559 + "dev": true, 1560 + "license": "MIT" 1561 + }, 1562 + "node_modules/@types/estree": { 1563 + "version": "1.0.8", 1564 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 1565 + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 1566 + "dev": true, 1567 + "license": "MIT" 1568 + }, 1569 + "node_modules/@types/trusted-types": { 1570 + "version": "2.0.7", 1571 + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", 1572 + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", 1573 + "dev": true, 1574 + "license": "MIT" 1575 + }, 1576 + "node_modules/@typescript-eslint/types": { 1577 + "version": "8.57.2", 1578 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", 1579 + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", 1580 + "dev": true, 1581 + "license": "MIT", 1582 + "engines": { 1583 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 1584 + }, 1585 + "funding": { 1586 + "type": "opencollective", 1587 + "url": "https://opencollective.com/typescript-eslint" 1588 + } 1589 + }, 1590 + "node_modules/acorn": { 1591 + "version": "8.16.0", 1592 + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", 1593 + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", 1594 + "dev": true, 1595 + "license": "MIT", 1596 + "bin": { 1597 + "acorn": "bin/acorn" 1598 + }, 1599 + "engines": { 1600 + "node": ">=0.4.0" 1601 + } 1602 + }, 1603 + "node_modules/aria-query": { 1604 + "version": "5.3.1", 1605 + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", 1606 + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", 1607 + "dev": true, 1608 + "license": "Apache-2.0", 1609 + "engines": { 1610 + "node": ">= 0.4" 1611 + } 1612 + }, 1613 + "node_modules/await-lock": { 1614 + "version": "2.2.2", 1615 + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", 1616 + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", 1617 + "license": "MIT" 1618 + }, 1619 + "node_modules/axobject-query": { 1620 + "version": "4.1.0", 1621 + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", 1622 + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", 1623 + "dev": true, 1624 + "license": "Apache-2.0", 1625 + "engines": { 1626 + "node": ">= 0.4" 1627 + } 1628 + }, 1629 + "node_modules/chess.js": { 1630 + "version": "1.4.0", 1631 + "resolved": "https://registry.npmjs.org/chess.js/-/chess.js-1.4.0.tgz", 1632 + "integrity": "sha512-BBJgrrtKQOzFLonR0l+k64A98NLemPwNsCskwb+29bRwobUa4iTm51E1kwGPbWXAcfdDa18nad6vpPPKPWarqw==", 1633 + "license": "BSD-2-Clause" 1634 + }, 1635 + "node_modules/chokidar": { 1636 + "version": "4.0.3", 1637 + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", 1638 + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", 1639 + "dev": true, 1640 + "license": "MIT", 1641 + "dependencies": { 1642 + "readdirp": "^4.0.1" 1643 + }, 1644 + "engines": { 1645 + "node": ">= 14.16.0" 1646 + }, 1647 + "funding": { 1648 + "url": "https://paulmillr.com/funding/" 1649 + } 1650 + }, 1651 + "node_modules/clsx": { 1652 + "version": "2.1.1", 1653 + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 1654 + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 1655 + "dev": true, 1656 + "license": "MIT", 1657 + "engines": { 1658 + "node": ">=6" 1659 + } 1660 + }, 1661 + "node_modules/cookie": { 1662 + "version": "0.6.0", 1663 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", 1664 + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", 1665 + "dev": true, 1666 + "license": "MIT", 1667 + "engines": { 1668 + "node": ">= 0.6" 1669 + } 1670 + }, 1671 + "node_modules/core-js": { 1672 + "version": "3.49.0", 1673 + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", 1674 + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", 1675 + "hasInstallScript": true, 1676 + "license": "MIT", 1677 + "funding": { 1678 + "type": "opencollective", 1679 + "url": "https://opencollective.com/core-js" 1680 + } 1681 + }, 1682 + "node_modules/deepmerge": { 1683 + "version": "4.3.1", 1684 + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 1685 + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 1686 + "dev": true, 1687 + "license": "MIT", 1688 + "engines": { 1689 + "node": ">=0.10.0" 1690 + } 1691 + }, 1692 + "node_modules/detect-libc": { 1693 + "version": "2.1.2", 1694 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1695 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1696 + "dev": true, 1697 + "license": "Apache-2.0", 1698 + "engines": { 1699 + "node": ">=8" 1700 + } 1701 + }, 1702 + "node_modules/devalue": { 1703 + "version": "5.6.4", 1704 + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", 1705 + "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", 1706 + "dev": true, 1707 + "license": "MIT" 1708 + }, 1709 + "node_modules/enhanced-resolve": { 1710 + "version": "5.20.1", 1711 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", 1712 + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", 1713 + "dev": true, 1714 + "license": "MIT", 1715 + "dependencies": { 1716 + "graceful-fs": "^4.2.4", 1717 + "tapable": "^2.3.0" 1718 + }, 1719 + "engines": { 1720 + "node": ">=10.13.0" 1721 + } 1722 + }, 1723 + "node_modules/esbuild": { 1724 + "version": "0.27.4", 1725 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", 1726 + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", 1727 + "dev": true, 1728 + "hasInstallScript": true, 1729 + "license": "MIT", 1730 + "bin": { 1731 + "esbuild": "bin/esbuild" 1732 + }, 1733 + "engines": { 1734 + "node": ">=18" 1735 + }, 1736 + "optionalDependencies": { 1737 + "@esbuild/aix-ppc64": "0.27.4", 1738 + "@esbuild/android-arm": "0.27.4", 1739 + "@esbuild/android-arm64": "0.27.4", 1740 + "@esbuild/android-x64": "0.27.4", 1741 + "@esbuild/darwin-arm64": "0.27.4", 1742 + "@esbuild/darwin-x64": "0.27.4", 1743 + "@esbuild/freebsd-arm64": "0.27.4", 1744 + "@esbuild/freebsd-x64": "0.27.4", 1745 + "@esbuild/linux-arm": "0.27.4", 1746 + "@esbuild/linux-arm64": "0.27.4", 1747 + "@esbuild/linux-ia32": "0.27.4", 1748 + "@esbuild/linux-loong64": "0.27.4", 1749 + "@esbuild/linux-mips64el": "0.27.4", 1750 + "@esbuild/linux-ppc64": "0.27.4", 1751 + "@esbuild/linux-riscv64": "0.27.4", 1752 + "@esbuild/linux-s390x": "0.27.4", 1753 + "@esbuild/linux-x64": "0.27.4", 1754 + "@esbuild/netbsd-arm64": "0.27.4", 1755 + "@esbuild/netbsd-x64": "0.27.4", 1756 + "@esbuild/openbsd-arm64": "0.27.4", 1757 + "@esbuild/openbsd-x64": "0.27.4", 1758 + "@esbuild/openharmony-arm64": "0.27.4", 1759 + "@esbuild/sunos-x64": "0.27.4", 1760 + "@esbuild/win32-arm64": "0.27.4", 1761 + "@esbuild/win32-ia32": "0.27.4", 1762 + "@esbuild/win32-x64": "0.27.4" 1763 + } 1764 + }, 1765 + "node_modules/esm-env": { 1766 + "version": "1.2.2", 1767 + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 1768 + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 1769 + "dev": true, 1770 + "license": "MIT" 1771 + }, 1772 + "node_modules/esrap": { 1773 + "version": "2.2.4", 1774 + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz", 1775 + "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==", 1776 + "dev": true, 1777 + "license": "MIT", 1778 + "dependencies": { 1779 + "@jridgewell/sourcemap-codec": "^1.4.15", 1780 + "@typescript-eslint/types": "^8.2.0" 1781 + } 1782 + }, 1783 + "node_modules/fdir": { 1784 + "version": "6.5.0", 1785 + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", 1786 + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", 1787 + "dev": true, 1788 + "license": "MIT", 1789 + "engines": { 1790 + "node": ">=12.0.0" 1791 + }, 1792 + "peerDependencies": { 1793 + "picomatch": "^3 || ^4" 1794 + }, 1795 + "peerDependenciesMeta": { 1796 + "picomatch": { 1797 + "optional": true 1798 + } 1799 + } 1800 + }, 1801 + "node_modules/fsevents": { 1802 + "version": "2.3.3", 1803 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1804 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1805 + "dev": true, 1806 + "hasInstallScript": true, 1807 + "license": "MIT", 1808 + "optional": true, 1809 + "os": [ 1810 + "darwin" 1811 + ], 1812 + "engines": { 1813 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1814 + } 1815 + }, 1816 + "node_modules/graceful-fs": { 1817 + "version": "4.2.11", 1818 + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1819 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1820 + "dev": true, 1821 + "license": "ISC" 1822 + }, 1823 + "node_modules/is-reference": { 1824 + "version": "3.0.3", 1825 + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", 1826 + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", 1827 + "dev": true, 1828 + "license": "MIT", 1829 + "dependencies": { 1830 + "@types/estree": "^1.0.6" 1831 + } 1832 + }, 1833 + "node_modules/iso-datestring-validator": { 1834 + "version": "2.2.2", 1835 + "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 1836 + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 1837 + "license": "MIT" 1838 + }, 1839 + "node_modules/jiti": { 1840 + "version": "2.6.1", 1841 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", 1842 + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", 1843 + "dev": true, 1844 + "license": "MIT", 1845 + "bin": { 1846 + "jiti": "lib/jiti-cli.mjs" 1847 + } 1848 + }, 1849 + "node_modules/jose": { 1850 + "version": "5.10.0", 1851 + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", 1852 + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", 1853 + "license": "MIT", 1854 + "funding": { 1855 + "url": "https://github.com/sponsors/panva" 1856 + } 1857 + }, 1858 + "node_modules/kleur": { 1859 + "version": "4.1.5", 1860 + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1861 + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1862 + "dev": true, 1863 + "license": "MIT", 1864 + "engines": { 1865 + "node": ">=6" 1866 + } 1867 + }, 1868 + "node_modules/lightningcss": { 1869 + "version": "1.32.0", 1870 + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", 1871 + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", 1872 + "dev": true, 1873 + "license": "MPL-2.0", 1874 + "dependencies": { 1875 + "detect-libc": "^2.0.3" 1876 + }, 1877 + "engines": { 1878 + "node": ">= 12.0.0" 1879 + }, 1880 + "funding": { 1881 + "type": "opencollective", 1882 + "url": "https://opencollective.com/parcel" 1883 + }, 1884 + "optionalDependencies": { 1885 + "lightningcss-android-arm64": "1.32.0", 1886 + "lightningcss-darwin-arm64": "1.32.0", 1887 + "lightningcss-darwin-x64": "1.32.0", 1888 + "lightningcss-freebsd-x64": "1.32.0", 1889 + "lightningcss-linux-arm-gnueabihf": "1.32.0", 1890 + "lightningcss-linux-arm64-gnu": "1.32.0", 1891 + "lightningcss-linux-arm64-musl": "1.32.0", 1892 + "lightningcss-linux-x64-gnu": "1.32.0", 1893 + "lightningcss-linux-x64-musl": "1.32.0", 1894 + "lightningcss-win32-arm64-msvc": "1.32.0", 1895 + "lightningcss-win32-x64-msvc": "1.32.0" 1896 + } 1897 + }, 1898 + "node_modules/lightningcss-android-arm64": { 1899 + "version": "1.32.0", 1900 + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", 1901 + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", 1902 + "cpu": [ 1903 + "arm64" 1904 + ], 1905 + "dev": true, 1906 + "license": "MPL-2.0", 1907 + "optional": true, 1908 + "os": [ 1909 + "android" 1910 + ], 1911 + "engines": { 1912 + "node": ">= 12.0.0" 1913 + }, 1914 + "funding": { 1915 + "type": "opencollective", 1916 + "url": "https://opencollective.com/parcel" 1917 + } 1918 + }, 1919 + "node_modules/lightningcss-darwin-arm64": { 1920 + "version": "1.32.0", 1921 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", 1922 + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", 1923 + "cpu": [ 1924 + "arm64" 1925 + ], 1926 + "dev": true, 1927 + "license": "MPL-2.0", 1928 + "optional": true, 1929 + "os": [ 1930 + "darwin" 1931 + ], 1932 + "engines": { 1933 + "node": ">= 12.0.0" 1934 + }, 1935 + "funding": { 1936 + "type": "opencollective", 1937 + "url": "https://opencollective.com/parcel" 1938 + } 1939 + }, 1940 + "node_modules/lightningcss-darwin-x64": { 1941 + "version": "1.32.0", 1942 + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", 1943 + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", 1944 + "cpu": [ 1945 + "x64" 1946 + ], 1947 + "dev": true, 1948 + "license": "MPL-2.0", 1949 + "optional": true, 1950 + "os": [ 1951 + "darwin" 1952 + ], 1953 + "engines": { 1954 + "node": ">= 12.0.0" 1955 + }, 1956 + "funding": { 1957 + "type": "opencollective", 1958 + "url": "https://opencollective.com/parcel" 1959 + } 1960 + }, 1961 + "node_modules/lightningcss-freebsd-x64": { 1962 + "version": "1.32.0", 1963 + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", 1964 + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", 1965 + "cpu": [ 1966 + "x64" 1967 + ], 1968 + "dev": true, 1969 + "license": "MPL-2.0", 1970 + "optional": true, 1971 + "os": [ 1972 + "freebsd" 1973 + ], 1974 + "engines": { 1975 + "node": ">= 12.0.0" 1976 + }, 1977 + "funding": { 1978 + "type": "opencollective", 1979 + "url": "https://opencollective.com/parcel" 1980 + } 1981 + }, 1982 + "node_modules/lightningcss-linux-arm-gnueabihf": { 1983 + "version": "1.32.0", 1984 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", 1985 + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", 1986 + "cpu": [ 1987 + "arm" 1988 + ], 1989 + "dev": true, 1990 + "license": "MPL-2.0", 1991 + "optional": true, 1992 + "os": [ 1993 + "linux" 1994 + ], 1995 + "engines": { 1996 + "node": ">= 12.0.0" 1997 + }, 1998 + "funding": { 1999 + "type": "opencollective", 2000 + "url": "https://opencollective.com/parcel" 2001 + } 2002 + }, 2003 + "node_modules/lightningcss-linux-arm64-gnu": { 2004 + "version": "1.32.0", 2005 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", 2006 + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", 2007 + "cpu": [ 2008 + "arm64" 2009 + ], 2010 + "dev": true, 2011 + "libc": [ 2012 + "glibc" 2013 + ], 2014 + "license": "MPL-2.0", 2015 + "optional": true, 2016 + "os": [ 2017 + "linux" 2018 + ], 2019 + "engines": { 2020 + "node": ">= 12.0.0" 2021 + }, 2022 + "funding": { 2023 + "type": "opencollective", 2024 + "url": "https://opencollective.com/parcel" 2025 + } 2026 + }, 2027 + "node_modules/lightningcss-linux-arm64-musl": { 2028 + "version": "1.32.0", 2029 + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", 2030 + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", 2031 + "cpu": [ 2032 + "arm64" 2033 + ], 2034 + "dev": true, 2035 + "libc": [ 2036 + "musl" 2037 + ], 2038 + "license": "MPL-2.0", 2039 + "optional": true, 2040 + "os": [ 2041 + "linux" 2042 + ], 2043 + "engines": { 2044 + "node": ">= 12.0.0" 2045 + }, 2046 + "funding": { 2047 + "type": "opencollective", 2048 + "url": "https://opencollective.com/parcel" 2049 + } 2050 + }, 2051 + "node_modules/lightningcss-linux-x64-gnu": { 2052 + "version": "1.32.0", 2053 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", 2054 + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", 2055 + "cpu": [ 2056 + "x64" 2057 + ], 2058 + "dev": true, 2059 + "libc": [ 2060 + "glibc" 2061 + ], 2062 + "license": "MPL-2.0", 2063 + "optional": true, 2064 + "os": [ 2065 + "linux" 2066 + ], 2067 + "engines": { 2068 + "node": ">= 12.0.0" 2069 + }, 2070 + "funding": { 2071 + "type": "opencollective", 2072 + "url": "https://opencollective.com/parcel" 2073 + } 2074 + }, 2075 + "node_modules/lightningcss-linux-x64-musl": { 2076 + "version": "1.32.0", 2077 + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", 2078 + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", 2079 + "cpu": [ 2080 + "x64" 2081 + ], 2082 + "dev": true, 2083 + "libc": [ 2084 + "musl" 2085 + ], 2086 + "license": "MPL-2.0", 2087 + "optional": true, 2088 + "os": [ 2089 + "linux" 2090 + ], 2091 + "engines": { 2092 + "node": ">= 12.0.0" 2093 + }, 2094 + "funding": { 2095 + "type": "opencollective", 2096 + "url": "https://opencollective.com/parcel" 2097 + } 2098 + }, 2099 + "node_modules/lightningcss-win32-arm64-msvc": { 2100 + "version": "1.32.0", 2101 + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", 2102 + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", 2103 + "cpu": [ 2104 + "arm64" 2105 + ], 2106 + "dev": true, 2107 + "license": "MPL-2.0", 2108 + "optional": true, 2109 + "os": [ 2110 + "win32" 2111 + ], 2112 + "engines": { 2113 + "node": ">= 12.0.0" 2114 + }, 2115 + "funding": { 2116 + "type": "opencollective", 2117 + "url": "https://opencollective.com/parcel" 2118 + } 2119 + }, 2120 + "node_modules/lightningcss-win32-x64-msvc": { 2121 + "version": "1.32.0", 2122 + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", 2123 + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", 2124 + "cpu": [ 2125 + "x64" 2126 + ], 2127 + "dev": true, 2128 + "license": "MPL-2.0", 2129 + "optional": true, 2130 + "os": [ 2131 + "win32" 2132 + ], 2133 + "engines": { 2134 + "node": ">= 12.0.0" 2135 + }, 2136 + "funding": { 2137 + "type": "opencollective", 2138 + "url": "https://opencollective.com/parcel" 2139 + } 2140 + }, 2141 + "node_modules/locate-character": { 2142 + "version": "3.0.0", 2143 + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 2144 + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", 2145 + "dev": true, 2146 + "license": "MIT" 2147 + }, 2148 + "node_modules/lru-cache": { 2149 + "version": "10.4.3", 2150 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", 2151 + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", 2152 + "license": "ISC" 2153 + }, 2154 + "node_modules/magic-string": { 2155 + "version": "0.30.21", 2156 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", 2157 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", 2158 + "dev": true, 2159 + "license": "MIT", 2160 + "dependencies": { 2161 + "@jridgewell/sourcemap-codec": "^1.5.5" 2162 + } 2163 + }, 2164 + "node_modules/mri": { 2165 + "version": "1.2.0", 2166 + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", 2167 + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", 2168 + "dev": true, 2169 + "license": "MIT", 2170 + "engines": { 2171 + "node": ">=4" 2172 + } 2173 + }, 2174 + "node_modules/mrmime": { 2175 + "version": "2.0.1", 2176 + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", 2177 + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", 2178 + "dev": true, 2179 + "license": "MIT", 2180 + "engines": { 2181 + "node": ">=10" 2182 + } 2183 + }, 2184 + "node_modules/multiformats": { 2185 + "version": "9.9.0", 2186 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 2187 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 2188 + "license": "(Apache-2.0 AND MIT)" 2189 + }, 2190 + "node_modules/nanoid": { 2191 + "version": "3.3.11", 2192 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 2193 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 2194 + "dev": true, 2195 + "funding": [ 2196 + { 2197 + "type": "github", 2198 + "url": "https://github.com/sponsors/ai" 2199 + } 2200 + ], 2201 + "license": "MIT", 2202 + "bin": { 2203 + "nanoid": "bin/nanoid.cjs" 2204 + }, 2205 + "engines": { 2206 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2207 + } 2208 + }, 2209 + "node_modules/obug": { 2210 + "version": "2.1.1", 2211 + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", 2212 + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", 2213 + "dev": true, 2214 + "funding": [ 2215 + "https://github.com/sponsors/sxzz", 2216 + "https://opencollective.com/debug" 2217 + ], 2218 + "license": "MIT" 2219 + }, 2220 + "node_modules/picocolors": { 2221 + "version": "1.1.1", 2222 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 2223 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 2224 + "dev": true, 2225 + "license": "ISC" 2226 + }, 2227 + "node_modules/picomatch": { 2228 + "version": "4.0.4", 2229 + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", 2230 + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", 2231 + "dev": true, 2232 + "license": "MIT", 2233 + "engines": { 2234 + "node": ">=12" 2235 + }, 2236 + "funding": { 2237 + "url": "https://github.com/sponsors/jonschlinkert" 2238 + } 2239 + }, 2240 + "node_modules/postcss": { 2241 + "version": "8.5.8", 2242 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", 2243 + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", 2244 + "dev": true, 2245 + "funding": [ 2246 + { 2247 + "type": "opencollective", 2248 + "url": "https://opencollective.com/postcss/" 2249 + }, 2250 + { 2251 + "type": "tidelift", 2252 + "url": "https://tidelift.com/funding/github/npm/postcss" 2253 + }, 2254 + { 2255 + "type": "github", 2256 + "url": "https://github.com/sponsors/ai" 2257 + } 2258 + ], 2259 + "license": "MIT", 2260 + "dependencies": { 2261 + "nanoid": "^3.3.11", 2262 + "picocolors": "^1.1.1", 2263 + "source-map-js": "^1.2.1" 2264 + }, 2265 + "engines": { 2266 + "node": "^10 || ^12 || >=14" 2267 + } 2268 + }, 2269 + "node_modules/readdirp": { 2270 + "version": "4.1.2", 2271 + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", 2272 + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", 2273 + "dev": true, 2274 + "license": "MIT", 2275 + "engines": { 2276 + "node": ">= 14.18.0" 2277 + }, 2278 + "funding": { 2279 + "type": "individual", 2280 + "url": "https://paulmillr.com/funding/" 2281 + } 2282 + }, 2283 + "node_modules/rollup": { 2284 + "version": "4.60.0", 2285 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", 2286 + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", 2287 + "dev": true, 2288 + "license": "MIT", 2289 + "dependencies": { 2290 + "@types/estree": "1.0.8" 2291 + }, 2292 + "bin": { 2293 + "rollup": "dist/bin/rollup" 2294 + }, 2295 + "engines": { 2296 + "node": ">=18.0.0", 2297 + "npm": ">=8.0.0" 2298 + }, 2299 + "optionalDependencies": { 2300 + "@rollup/rollup-android-arm-eabi": "4.60.0", 2301 + "@rollup/rollup-android-arm64": "4.60.0", 2302 + "@rollup/rollup-darwin-arm64": "4.60.0", 2303 + "@rollup/rollup-darwin-x64": "4.60.0", 2304 + "@rollup/rollup-freebsd-arm64": "4.60.0", 2305 + "@rollup/rollup-freebsd-x64": "4.60.0", 2306 + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", 2307 + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", 2308 + "@rollup/rollup-linux-arm64-gnu": "4.60.0", 2309 + "@rollup/rollup-linux-arm64-musl": "4.60.0", 2310 + "@rollup/rollup-linux-loong64-gnu": "4.60.0", 2311 + "@rollup/rollup-linux-loong64-musl": "4.60.0", 2312 + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", 2313 + "@rollup/rollup-linux-ppc64-musl": "4.60.0", 2314 + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", 2315 + "@rollup/rollup-linux-riscv64-musl": "4.60.0", 2316 + "@rollup/rollup-linux-s390x-gnu": "4.60.0", 2317 + "@rollup/rollup-linux-x64-gnu": "4.60.0", 2318 + "@rollup/rollup-linux-x64-musl": "4.60.0", 2319 + "@rollup/rollup-openbsd-x64": "4.60.0", 2320 + "@rollup/rollup-openharmony-arm64": "4.60.0", 2321 + "@rollup/rollup-win32-arm64-msvc": "4.60.0", 2322 + "@rollup/rollup-win32-ia32-msvc": "4.60.0", 2323 + "@rollup/rollup-win32-x64-gnu": "4.60.0", 2324 + "@rollup/rollup-win32-x64-msvc": "4.60.0", 2325 + "fsevents": "~2.3.2" 2326 + } 2327 + }, 2328 + "node_modules/sade": { 2329 + "version": "1.8.1", 2330 + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", 2331 + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", 2332 + "dev": true, 2333 + "license": "MIT", 2334 + "dependencies": { 2335 + "mri": "^1.1.0" 2336 + }, 2337 + "engines": { 2338 + "node": ">=6" 2339 + } 2340 + }, 2341 + "node_modules/set-cookie-parser": { 2342 + "version": "3.1.0", 2343 + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", 2344 + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", 2345 + "dev": true, 2346 + "license": "MIT" 2347 + }, 2348 + "node_modules/sirv": { 2349 + "version": "3.0.2", 2350 + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", 2351 + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", 2352 + "dev": true, 2353 + "license": "MIT", 2354 + "dependencies": { 2355 + "@polka/url": "^1.0.0-next.24", 2356 + "mrmime": "^2.0.0", 2357 + "totalist": "^3.0.0" 2358 + }, 2359 + "engines": { 2360 + "node": ">=18" 2361 + } 2362 + }, 2363 + "node_modules/source-map-js": { 2364 + "version": "1.2.1", 2365 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 2366 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 2367 + "dev": true, 2368 + "license": "BSD-3-Clause", 2369 + "engines": { 2370 + "node": ">=0.10.0" 2371 + } 2372 + }, 2373 + "node_modules/svelte": { 2374 + "version": "5.55.0", 2375 + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.0.tgz", 2376 + "integrity": "sha512-SThllKq6TRMBwPtat7ASnm/9CDXnIhBR0NPGw0ujn2DVYx9rVwsPZxDaDQcYGdUz/3BYVsCzdq7pZarRQoGvtw==", 2377 + "dev": true, 2378 + "license": "MIT", 2379 + "dependencies": { 2380 + "@jridgewell/remapping": "^2.3.4", 2381 + "@jridgewell/sourcemap-codec": "^1.5.0", 2382 + "@sveltejs/acorn-typescript": "^1.0.5", 2383 + "@types/estree": "^1.0.5", 2384 + "@types/trusted-types": "^2.0.7", 2385 + "acorn": "^8.12.1", 2386 + "aria-query": "5.3.1", 2387 + "axobject-query": "^4.1.0", 2388 + "clsx": "^2.1.1", 2389 + "devalue": "^5.6.4", 2390 + "esm-env": "^1.2.1", 2391 + "esrap": "^2.2.2", 2392 + "is-reference": "^3.0.3", 2393 + "locate-character": "^3.0.0", 2394 + "magic-string": "^0.30.11", 2395 + "zimmerframe": "^1.1.2" 2396 + }, 2397 + "engines": { 2398 + "node": ">=18" 2399 + } 2400 + }, 2401 + "node_modules/svelte-check": { 2402 + "version": "4.4.5", 2403 + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.5.tgz", 2404 + "integrity": "sha512-1bSwIRCvvmSHrlK52fOlZmVtUZgil43jNL/2H18pRpa+eQjzGt6e3zayxhp1S7GajPFKNM/2PMCG+DZFHlG9fw==", 2405 + "dev": true, 2406 + "license": "MIT", 2407 + "dependencies": { 2408 + "@jridgewell/trace-mapping": "^0.3.25", 2409 + "chokidar": "^4.0.1", 2410 + "fdir": "^6.2.0", 2411 + "picocolors": "^1.0.0", 2412 + "sade": "^1.7.4" 2413 + }, 2414 + "bin": { 2415 + "svelte-check": "bin/svelte-check" 2416 + }, 2417 + "engines": { 2418 + "node": ">= 18.0.0" 2419 + }, 2420 + "peerDependencies": { 2421 + "svelte": "^4.0.0 || ^5.0.0-next.0", 2422 + "typescript": ">=5.0.0" 2423 + } 2424 + }, 2425 + "node_modules/tailwindcss": { 2426 + "version": "4.2.2", 2427 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", 2428 + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", 2429 + "dev": true, 2430 + "license": "MIT" 2431 + }, 2432 + "node_modules/tapable": { 2433 + "version": "2.3.2", 2434 + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", 2435 + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", 2436 + "dev": true, 2437 + "license": "MIT", 2438 + "engines": { 2439 + "node": ">=6" 2440 + }, 2441 + "funding": { 2442 + "type": "opencollective", 2443 + "url": "https://opencollective.com/webpack" 2444 + } 2445 + }, 2446 + "node_modules/tinyglobby": { 2447 + "version": "0.2.15", 2448 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 2449 + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 2450 + "dev": true, 2451 + "license": "MIT", 2452 + "dependencies": { 2453 + "fdir": "^6.5.0", 2454 + "picomatch": "^4.0.3" 2455 + }, 2456 + "engines": { 2457 + "node": ">=12.0.0" 2458 + }, 2459 + "funding": { 2460 + "url": "https://github.com/sponsors/SuperchupuDev" 2461 + } 2462 + }, 2463 + "node_modules/tlds": { 2464 + "version": "1.261.0", 2465 + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", 2466 + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", 2467 + "license": "MIT", 2468 + "bin": { 2469 + "tlds": "bin.js" 2470 + } 2471 + }, 2472 + "node_modules/totalist": { 2473 + "version": "3.0.1", 2474 + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", 2475 + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", 2476 + "dev": true, 2477 + "license": "MIT", 2478 + "engines": { 2479 + "node": ">=6" 2480 + } 2481 + }, 2482 + "node_modules/tslib": { 2483 + "version": "2.8.1", 2484 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2485 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2486 + "license": "0BSD" 2487 + }, 2488 + "node_modules/typescript": { 2489 + "version": "5.9.3", 2490 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 2491 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 2492 + "dev": true, 2493 + "license": "Apache-2.0", 2494 + "bin": { 2495 + "tsc": "bin/tsc", 2496 + "tsserver": "bin/tsserver" 2497 + }, 2498 + "engines": { 2499 + "node": ">=14.17" 2500 + } 2501 + }, 2502 + "node_modules/uint8arrays": { 2503 + "version": "3.0.0", 2504 + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 2505 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 2506 + "license": "MIT", 2507 + "dependencies": { 2508 + "multiformats": "^9.4.2" 2509 + } 2510 + }, 2511 + "node_modules/unicode-segmenter": { 2512 + "version": "0.14.5", 2513 + "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz", 2514 + "integrity": "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==", 2515 + "license": "MIT" 2516 + }, 2517 + "node_modules/vite": { 2518 + "version": "7.3.1", 2519 + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", 2520 + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", 2521 + "dev": true, 2522 + "license": "MIT", 2523 + "dependencies": { 2524 + "esbuild": "^0.27.0", 2525 + "fdir": "^6.5.0", 2526 + "picomatch": "^4.0.3", 2527 + "postcss": "^8.5.6", 2528 + "rollup": "^4.43.0", 2529 + "tinyglobby": "^0.2.15" 2530 + }, 2531 + "bin": { 2532 + "vite": "bin/vite.js" 2533 + }, 2534 + "engines": { 2535 + "node": "^20.19.0 || >=22.12.0" 2536 + }, 2537 + "funding": { 2538 + "url": "https://github.com/vitejs/vite?sponsor=1" 2539 + }, 2540 + "optionalDependencies": { 2541 + "fsevents": "~2.3.3" 2542 + }, 2543 + "peerDependencies": { 2544 + "@types/node": "^20.19.0 || >=22.12.0", 2545 + "jiti": ">=1.21.0", 2546 + "less": "^4.0.0", 2547 + "lightningcss": "^1.21.0", 2548 + "sass": "^1.70.0", 2549 + "sass-embedded": "^1.70.0", 2550 + "stylus": ">=0.54.8", 2551 + "sugarss": "^5.0.0", 2552 + "terser": "^5.16.0", 2553 + "tsx": "^4.8.1", 2554 + "yaml": "^2.4.2" 2555 + }, 2556 + "peerDependenciesMeta": { 2557 + "@types/node": { 2558 + "optional": true 2559 + }, 2560 + "jiti": { 2561 + "optional": true 2562 + }, 2563 + "less": { 2564 + "optional": true 2565 + }, 2566 + "lightningcss": { 2567 + "optional": true 2568 + }, 2569 + "sass": { 2570 + "optional": true 2571 + }, 2572 + "sass-embedded": { 2573 + "optional": true 2574 + }, 2575 + "stylus": { 2576 + "optional": true 2577 + }, 2578 + "sugarss": { 2579 + "optional": true 2580 + }, 2581 + "terser": { 2582 + "optional": true 2583 + }, 2584 + "tsx": { 2585 + "optional": true 2586 + }, 2587 + "yaml": { 2588 + "optional": true 2589 + } 2590 + } 2591 + }, 2592 + "node_modules/vitefu": { 2593 + "version": "1.1.2", 2594 + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", 2595 + "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", 2596 + "dev": true, 2597 + "license": "MIT", 2598 + "workspaces": [ 2599 + "tests/deps/*", 2600 + "tests/projects/*", 2601 + "tests/projects/workspace/packages/*" 2602 + ], 2603 + "peerDependencies": { 2604 + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" 2605 + }, 2606 + "peerDependenciesMeta": { 2607 + "vite": { 2608 + "optional": true 2609 + } 2610 + } 2611 + }, 2612 + "node_modules/zimmerframe": { 2613 + "version": "1.1.4", 2614 + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", 2615 + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", 2616 + "dev": true, 2617 + "license": "MIT" 2618 + }, 2619 + "node_modules/zod": { 2620 + "version": "3.25.76", 2621 + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", 2622 + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", 2623 + "license": "MIT", 2624 + "funding": { 2625 + "url": "https://github.com/sponsors/colinhacks" 2626 + } 2627 + } 2628 + } 2629 + }
+31
package.json
··· 1 + { 2 + "name": "checkmate.blue", 3 + "private": true, 4 + "version": "0.0.1", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite dev", 8 + "build": "vite build", 9 + "preview": "vite preview", 10 + "prepare": "svelte-kit sync || echo ''", 11 + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 13 + }, 14 + "devDependencies": { 15 + "@sveltejs/adapter-static": "^3.0.10", 16 + "@sveltejs/kit": "^2.50.2", 17 + "@sveltejs/vite-plugin-svelte": "^6.2.4", 18 + "@tailwindcss/vite": "^4.1.18", 19 + "svelte": "^5.54.0", 20 + "svelte-check": "^4.4.2", 21 + "tailwindcss": "^4.1.18", 22 + "typescript": "^5.9.3", 23 + "vite": "^7.3.1" 24 + }, 25 + "dependencies": { 26 + "@atproto/api": "^0.19.5", 27 + "@atproto/oauth-client-browser": "^0.3.41", 28 + "@lichess-org/chessground": "^10.1.1", 29 + "chess.js": "^1.4.0" 30 + } 31 + }
+49
scripts/delete-temp-records.ts
··· 1 + import { AtpAgent } from '@atproto/api'; 2 + 3 + const IDENTIFIER = process.argv[2]; 4 + const APP_PASSWORD = process.argv[3]; 5 + 6 + if (!IDENTIFIER || !APP_PASSWORD) { 7 + console.error('Usage: npx tsx scripts/delete-temp-records.ts <handle> <app-password>'); 8 + process.exit(1); 9 + } 10 + 11 + const COLLECTIONS = [ 12 + 'blue.checkmate.game', 13 + 'blue.checkmate.challenge', 14 + ]; 15 + 16 + async function main() { 17 + const agent = new AtpAgent({ service: 'https://bsky.social' }); 18 + await agent.login({ identifier: IDENTIFIER, password: APP_PASSWORD }); 19 + console.log(`Logged in as ${agent.session?.did}`); 20 + 21 + for (const collection of COLLECTIONS) { 22 + console.log(`\nListing ${collection}...`); 23 + const res = await agent.com.atproto.repo.listRecords({ 24 + repo: agent.session!.did, 25 + collection, 26 + limit: 100, 27 + }); 28 + 29 + if (res.data.records.length === 0) { 30 + console.log(' No records found.'); 31 + continue; 32 + } 33 + 34 + for (const record of res.data.records) { 35 + const rkey = record.uri.split('/').pop()!; 36 + console.log(` Deleting ${rkey}...`); 37 + await agent.com.atproto.repo.deleteRecord({ 38 + repo: agent.session!.did, 39 + collection, 40 + rkey, 41 + }); 42 + console.log(` Deleted.`); 43 + } 44 + } 45 + 46 + console.log('\nDone. All blue.checkmate.* records deleted.'); 47 + } 48 + 49 + main();
+13
src/app.d.ts
··· 1 + // See https://svelte.dev/docs/kit/types#app.d.ts 2 + // for information about these interfaces 3 + declare global { 4 + namespace App { 5 + // interface Error {} 6 + // interface Locals {} 7 + // interface PageData {} 8 + // interface PageState {} 9 + // interface Platform {} 10 + } 11 + } 12 + 13 + export {};
+12
src/app.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 + <title>checkmate.blue</title> 7 + %sveltekit.head% 8 + </head> 9 + <body data-sveltekit-preload-data="hover"> 10 + <div style="display: contents">%sveltekit.body%</div> 11 + </body> 12 + </html>
+197
src/lib/atproto.ts
··· 1 + import { Agent } from '@atproto/api'; 2 + import type { GameRecord, ChallengeRecord } from '$lib/types'; 3 + 4 + const COLLECTIONS = { 5 + game: 'blue.checkmate.game', 6 + challenge: 'blue.checkmate.challenge', 7 + } as const; 8 + 9 + const PLC_DIRECTORY = 'https://plc.directory'; 10 + 11 + async function resolvePdsUrl(did: string): Promise<string> { 12 + const res = await fetch(`${PLC_DIRECTORY}/${encodeURIComponent(did)}`); 13 + if (!res.ok) throw new Error(`Failed to resolve DID: ${did}`); 14 + const doc = await res.json(); 15 + const pdsService = doc.service?.find( 16 + (s: { id: string; type: string }) => s.id === '#atproto_pds' 17 + ); 18 + if (!pdsService?.serviceEndpoint) { 19 + throw new Error(`No PDS found for DID: ${did}`); 20 + } 21 + return pdsService.serviceEndpoint; 22 + } 23 + 24 + async function getPublicAgent(did: string): Promise<Agent> { 25 + const pdsUrl = await resolvePdsUrl(did); 26 + return new Agent(pdsUrl); 27 + } 28 + 29 + export async function createGame( 30 + agent: Agent, 31 + options: { white: string; black?: string; status?: GameRecord['status']; parentGameUri?: string } 32 + ): Promise<{ uri: string; rkey: string }> { 33 + const record: GameRecord = { 34 + $type: 'blue.checkmate.game', 35 + pgn: '', 36 + createdAt: new Date().toISOString(), 37 + white: options.white, 38 + black: options.black, 39 + status: options.status ?? 'waiting', 40 + parentGameUri: options.parentGameUri, 41 + }; 42 + 43 + const response = await agent.com.atproto.repo.createRecord({ 44 + repo: agent.assertDid, 45 + collection: COLLECTIONS.game, 46 + record, 47 + }); 48 + 49 + const rkey = response.data.uri.split('/').pop()!; 50 + return { uri: response.data.uri, rkey }; 51 + } 52 + 53 + export async function updateGame( 54 + agent: Agent, 55 + rkey: string, 56 + updates: Partial<Omit<GameRecord, '$type'>> 57 + ): Promise<void> { 58 + const did = agent.assertDid; 59 + const existing = await agent.com.atproto.repo.getRecord({ 60 + repo: did, 61 + collection: COLLECTIONS.game, 62 + rkey, 63 + }); 64 + 65 + const record = { 66 + ...existing.data.value, 67 + ...updates, 68 + $type: 'blue.checkmate.game', 69 + }; 70 + 71 + console.log('[updateGame]', { did, rkey, collection: COLLECTIONS.game }); 72 + 73 + await agent.com.atproto.repo.putRecord({ 74 + repo: did, 75 + collection: COLLECTIONS.game, 76 + rkey, 77 + record, 78 + swapRecord: existing.data.cid, 79 + }); 80 + } 81 + 82 + export async function getGame( 83 + agent: Agent, 84 + did: string, 85 + rkey: string 86 + ): Promise<GameRecord | null> { 87 + try { 88 + // Use the authenticated agent for own records, public agent for others 89 + const readAgent = did === agent.assertDid 90 + ? agent 91 + : await getPublicAgent(did); 92 + 93 + const response = await readAgent.com.atproto.repo.getRecord({ 94 + repo: did, 95 + collection: COLLECTIONS.game, 96 + rkey, 97 + }); 98 + return response.data.value as unknown as GameRecord; 99 + } catch (e) { 100 + console.error('getGame failed:', did, rkey, e); 101 + return null; 102 + } 103 + } 104 + 105 + export async function createChallenge( 106 + agent: Agent, 107 + options: { opponent?: string } 108 + ): Promise<{ uri: string; rkey: string }> { 109 + const record: ChallengeRecord = { 110 + $type: 'blue.checkmate.challenge', 111 + createdAt: new Date().toISOString(), 112 + opponent: options.opponent, 113 + status: 'open', 114 + }; 115 + 116 + const response = await agent.com.atproto.repo.createRecord({ 117 + repo: agent.assertDid, 118 + collection: COLLECTIONS.challenge, 119 + record, 120 + }); 121 + 122 + const rkey = response.data.uri.split('/').pop()!; 123 + return { uri: response.data.uri, rkey }; 124 + } 125 + 126 + export async function updateChallenge( 127 + agent: Agent, 128 + rkey: string, 129 + updates: Partial<Omit<ChallengeRecord, '$type'>> 130 + ): Promise<void> { 131 + const existing = await agent.com.atproto.repo.getRecord({ 132 + repo: agent.assertDid, 133 + collection: COLLECTIONS.challenge, 134 + rkey, 135 + }); 136 + 137 + const record = { 138 + ...existing.data.value, 139 + ...updates, 140 + $type: 'blue.checkmate.challenge', 141 + }; 142 + 143 + await agent.com.atproto.repo.putRecord({ 144 + repo: agent.assertDid, 145 + collection: COLLECTIONS.challenge, 146 + rkey, 147 + record, 148 + }); 149 + } 150 + 151 + export async function getChallenge( 152 + agent: Agent, 153 + did: string, 154 + rkey: string 155 + ): Promise<ChallengeRecord | null> { 156 + try { 157 + const readAgent = did === agent.assertDid 158 + ? agent 159 + : await getPublicAgent(did); 160 + 161 + const response = await readAgent.com.atproto.repo.getRecord({ 162 + repo: did, 163 + collection: COLLECTIONS.challenge, 164 + rkey, 165 + }); 166 + return response.data.value as unknown as ChallengeRecord; 167 + } catch (e) { 168 + console.error('getChallenge failed:', did, rkey, e); 169 + return null; 170 + } 171 + } 172 + 173 + export async function findGameRecordByParent( 174 + agent: Agent, 175 + did: string, 176 + parentGameUri: string 177 + ): Promise<{ rkey: string; record: GameRecord } | null> { 178 + const readAgent = did === agent.assertDid 179 + ? agent 180 + : await getPublicAgent(did); 181 + 182 + const response = await readAgent.com.atproto.repo.listRecords({ 183 + repo: did, 184 + collection: COLLECTIONS.game, 185 + limit: 100, 186 + }); 187 + 188 + for (const rec of response.data.records) { 189 + const value = rec.value as unknown as GameRecord; 190 + if (value.parentGameUri === parentGameUri) { 191 + return { rkey: rec.uri.split('/').pop()!, record: value }; 192 + } 193 + } 194 + return null; 195 + } 196 + 197 + export { COLLECTIONS };
+77
src/lib/components/Board.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import { Chessground } from '@lichess-org/chessground'; 4 + import type { Api as CgApi } from '@lichess-org/chessground/api'; 5 + import type { Config as CgConfig } from '@lichess-org/chessground/config'; 6 + import type { Dests } from '$lib/game-logic'; 7 + 8 + type Props = { 9 + fen?: string; 10 + orientation?: 'white' | 'black'; 11 + turnColor?: 'white' | 'black'; 12 + dests?: Dests; 13 + lastMove?: [string, string]; 14 + viewOnly?: boolean; 15 + onmove?: (orig: string, dest: string) => void; 16 + }; 17 + 18 + let { 19 + fen = 'start', 20 + orientation = 'white', 21 + turnColor = 'white', 22 + dests, 23 + lastMove, 24 + viewOnly = false, 25 + onmove, 26 + }: Props = $props(); 27 + 28 + let boardEl: HTMLDivElement; 29 + let cg: CgApi | null = null; 30 + 31 + onMount(() => { 32 + cg = Chessground(boardEl, buildConfig()); 33 + return () => cg?.destroy(); 34 + }); 35 + 36 + $effect(() => { 37 + cg?.set(buildConfig()); 38 + }); 39 + 40 + function buildConfig(): CgConfig { 41 + return { 42 + fen, 43 + orientation, 44 + turnColor, 45 + lastMove: lastMove as [string, string] | undefined, 46 + viewOnly, 47 + movable: { 48 + free: false, 49 + color: viewOnly ? undefined : orientation, 50 + dests: dests as Map<string, string[]> | undefined, 51 + showDests: true, 52 + }, 53 + events: { 54 + move: (orig, dest) => onmove?.(orig, dest), 55 + }, 56 + draggable: { showGhost: true }, 57 + premovable: { enabled: false }, 58 + animation: { enabled: true, duration: 200 }, 59 + }; 60 + } 61 + </script> 62 + 63 + <div class="board-wrap"> 64 + <div bind:this={boardEl} class="cg-wrap"></div> 65 + </div> 66 + 67 + <style> 68 + .board-wrap { 69 + width: 100%; 70 + max-width: min(90vw, 560px); 71 + aspect-ratio: 1; 72 + } 73 + .cg-wrap { 74 + width: 100%; 75 + height: 100%; 76 + } 77 + </style>
+26
src/lib/components/GameControls.svelte
··· 1 + <script lang="ts"> 2 + type Props = { 3 + onresign?: () => void; 4 + ondrawoffer?: () => void; 5 + gameOver?: boolean; 6 + }; 7 + 8 + let { onresign, ondrawoffer, gameOver = false }: Props = $props(); 9 + </script> 10 + 11 + {#if !gameOver} 12 + <div class="flex gap-3"> 13 + <button 14 + onclick={() => onresign?.()} 15 + class="rounded-lg border border-danger px-4 py-2 text-sm text-danger transition-colors hover:bg-danger hover:text-white" 16 + > 17 + Resign 18 + </button> 19 + <button 20 + onclick={() => ondrawoffer?.()} 21 + class="rounded-lg border border-border px-4 py-2 text-sm text-text-secondary transition-colors hover:text-text-primary" 22 + > 23 + Offer Draw 24 + </button> 25 + </div> 26 + {/if}
+38
src/lib/components/LoginButton.svelte
··· 1 + <script lang="ts"> 2 + import { auth } from '$lib/stores/auth.svelte'; 3 + 4 + let handleInput = $state(''); 5 + let isLoading = $state(false); 6 + let error = $state(''); 7 + 8 + async function handleSignIn() { 9 + if (!handleInput.trim()) return; 10 + isLoading = true; 11 + error = ''; 12 + try { 13 + await auth.signIn(handleInput.trim()); 14 + } catch (e) { 15 + error = e instanceof Error ? e.message : 'Sign in failed'; 16 + isLoading = false; 17 + } 18 + } 19 + </script> 20 + 21 + <form onsubmit={handleSignIn} class="flex flex-col gap-3"> 22 + <input 23 + type="text" 24 + bind:value={handleInput} 25 + placeholder="your-handle.bsky.social" 26 + class="rounded-lg border border-border bg-bg-secondary px-4 py-2 text-text-primary placeholder:text-text-secondary focus:border-accent-blue focus:outline-none" 27 + /> 28 + <button 29 + type="submit" 30 + disabled={isLoading || !handleInput.trim()} 31 + class="rounded-lg bg-accent-blue px-4 py-2 font-semibold text-white transition-colors hover:bg-accent-blue-hover disabled:opacity-50" 32 + > 33 + {isLoading ? 'Signing in...' : 'Sign in with Bluesky'} 34 + </button> 35 + {#if error} 36 + <p class="text-sm text-danger">{error}</p> 37 + {/if} 38 + </form>
+22
src/lib/components/MoveList.svelte
··· 1 + <script lang="ts"> 2 + import type { Chess } from 'chess.js'; 3 + 4 + type Props = { chess: Chess }; 5 + let { chess }: Props = $props(); 6 + 7 + const moves = $derived(chess.history()); 8 + </script> 9 + 10 + <div class="max-h-48 overflow-y-auto rounded-lg bg-bg-secondary p-3 font-mono text-sm"> 11 + {#if moves.length === 0} 12 + <p class="text-text-secondary">No moves yet</p> 13 + {:else} 14 + <div class="grid grid-cols-[2rem_1fr_1fr] gap-x-2 gap-y-0.5"> 15 + {#each Array(Math.ceil(moves.length / 2)) as _, i} 16 + <span class="text-text-secondary">{i + 1}.</span> 17 + <span>{moves[i * 2]}</span> 18 + <span>{moves[i * 2 + 1] ?? ''}</span> 19 + {/each} 20 + </div> 21 + {/if} 22 + </div>
+15
src/lib/components/PlayerBar.svelte
··· 1 + <script lang="ts"> 2 + type Props = { 3 + handle?: string; 4 + isActive?: boolean; 5 + }; 6 + 7 + let { handle = 'Waiting...', isActive = false }: Props = $props(); 8 + </script> 9 + 10 + <div class="flex items-center gap-3 rounded-lg bg-bg-secondary px-4 py-2" class:border-l-4={isActive} class:border-accent-blue={isActive}> 11 + <div class="flex h-8 w-8 items-center justify-center rounded-full bg-bg-board text-sm font-bold"> 12 + {handle[0]?.toUpperCase() ?? '?'} 13 + </div> 14 + <span class="truncate text-sm font-medium">{handle}</span> 15 + </div>
+36
src/lib/components/PromotionModal.svelte
··· 1 + <script lang="ts"> 2 + import type { PieceSymbol } from 'chess.js'; 3 + 4 + type Props = { 5 + color: 'white' | 'black'; 6 + onselect: (piece: PieceSymbol) => void; 7 + }; 8 + 9 + let { color, onselect }: Props = $props(); 10 + 11 + const pieces: { symbol: PieceSymbol; label: string }[] = [ 12 + { symbol: 'q', label: 'Queen' }, 13 + { symbol: 'r', label: 'Rook' }, 14 + { symbol: 'b', label: 'Bishop' }, 15 + { symbol: 'n', label: 'Knight' }, 16 + ]; 17 + 18 + const unicodePieces: Record<string, Record<string, string>> = { 19 + white: { q: '\u2655', r: '\u2656', b: '\u2657', n: '\u2658' }, 20 + black: { q: '\u265B', r: '\u265C', b: '\u265D', n: '\u265E' }, 21 + }; 22 + </script> 23 + 24 + <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60"> 25 + <div class="flex gap-2 rounded-xl bg-bg-secondary p-4 shadow-lg"> 26 + {#each pieces as { symbol, label }} 27 + <button 28 + onclick={() => onselect(symbol)} 29 + title={label} 30 + class="flex h-16 w-16 items-center justify-center rounded-lg text-4xl transition-colors hover:bg-bg-board" 31 + > 32 + {unicodePieces[color][symbol]} 33 + </button> 34 + {/each} 35 + </div> 36 + </div>
+78
src/lib/game-logic.ts
··· 1 + import { Chess, SQUARES, type Square, type PieceSymbol } from 'chess.js'; 2 + 3 + export type Dests = Map<string, string[]>; 4 + 5 + export function toDests(chess: Chess): Dests { 6 + const dests: Dests = new Map(); 7 + for (const s of SQUARES) { 8 + const moves = chess.moves({ square: s, verbose: true }); 9 + if (moves.length) { 10 + dests.set(s, moves.map((m) => m.to)); 11 + } 12 + } 13 + return dests; 14 + } 15 + 16 + export function isPromotion(chess: Chess, orig: string, dest: string): boolean { 17 + const moves = chess.moves({ square: orig as Square, verbose: true }); 18 + return moves.some((m) => m.to === dest && m.promotion); 19 + } 20 + 21 + export function applyMove( 22 + chess: Chess, 23 + orig: string, 24 + dest: string, 25 + promotion?: PieceSymbol 26 + ): boolean { 27 + const move = chess.move({ from: orig as Square, to: dest as Square, promotion }); 28 + return move !== null; 29 + } 30 + 31 + export function turnColor(chess: Chess): 'white' | 'black' { 32 + return chess.turn() === 'w' ? 'white' : 'black'; 33 + } 34 + 35 + export function lastMoveSquares(chess: Chess): [string, string] | undefined { 36 + const history = chess.history({ verbose: true }); 37 + if (history.length === 0) return undefined; 38 + const last = history[history.length - 1]; 39 + return [last.from, last.to]; 40 + } 41 + 42 + export function gameResult(chess: Chess): { 43 + result: '1-0' | '0-1' | '1/2-1/2'; 44 + reason: string; 45 + } | null { 46 + if (!chess.isGameOver()) return null; 47 + 48 + if (chess.isCheckmate()) { 49 + return { 50 + result: chess.turn() === 'w' ? '0-1' : '1-0', 51 + reason: 'checkmate', 52 + }; 53 + } 54 + if (chess.isStalemate()) return { result: '1/2-1/2', reason: 'stalemate' }; 55 + if (chess.isInsufficientMaterial()) return { result: '1/2-1/2', reason: 'insufficient' }; 56 + if (chess.isThreefoldRepetition()) return { result: '1/2-1/2', reason: 'repetition' }; 57 + if (chess.isDraw()) return { result: '1/2-1/2', reason: 'fifty_moves' }; 58 + 59 + return null; 60 + } 61 + 62 + export function makePgn(chess: Chess, white?: string, black?: string): string { 63 + const headers: [string, string][] = [ 64 + ['Event', 'checkmate.blue'], 65 + ['Site', 'https://checkmate.blue'], 66 + ['Date', new Date().toISOString().slice(0, 10).replace(/-/g, '.')], 67 + ]; 68 + if (white) headers.push(['White', white]); 69 + if (black) headers.push(['Black', black]); 70 + 71 + const result = gameResult(chess); 72 + headers.push(['Result', result?.result ?? '*']); 73 + 74 + for (const [key, value] of headers) { 75 + chess.header(key, value); 76 + } 77 + return chess.pgn(); 78 + }
+1
src/lib/index.ts
··· 1 + // place files you want to import through the `$lib` alias in this folder.
+154
src/lib/jetstream.ts
··· 1 + import { getGame, COLLECTIONS } from '$lib/atproto'; 2 + import type { Agent } from '@atproto/api'; 3 + 4 + export type JetstreamEvent = { 5 + kind: 'commit'; 6 + did: string; 7 + commit: { 8 + operation: 'create' | 'update' | 'delete'; 9 + collection: string; 10 + rkey: string; 11 + record?: Record<string, unknown>; 12 + cid?: string; 13 + }; 14 + time_us: number; 15 + }; 16 + 17 + type JetstreamOptions = { 18 + opponentDid: string; 19 + onGameUpdate: (record: Record<string, unknown>) => void; 20 + onConnectionChange?: (connected: boolean) => void; 21 + }; 22 + 23 + const JETSTREAM_URL = 'wss://jetstream2.us-east.bsky.network/subscribe'; 24 + const RECONNECT_BASE_MS = 1000; 25 + const RECONNECT_MAX_MS = 30000; 26 + const POLL_INTERVAL_MS = 3000; 27 + 28 + export class JetstreamConnection { 29 + private ws: WebSocket | null = null; 30 + private options: JetstreamOptions; 31 + private reconnectAttempts = 0; 32 + private reconnectTimer: ReturnType<typeof setTimeout> | null = null; 33 + private pollTimer: ReturnType<typeof setInterval> | null = null; 34 + private cursor: number | null = null; 35 + private destroyed = false; 36 + private agent: Agent | null = null; 37 + private opponentRkey: string | null = null; 38 + 39 + constructor(options: JetstreamOptions) { 40 + this.options = options; 41 + } 42 + 43 + connect(): void { 44 + if (this.destroyed) return; 45 + this.cleanup(); 46 + 47 + const url = new URL(JETSTREAM_URL); 48 + url.searchParams.set('wantedCollections', COLLECTIONS.game); 49 + if (this.options.opponentDid) { 50 + url.searchParams.set('wantedDids', this.options.opponentDid); 51 + } 52 + if (this.cursor) { 53 + url.searchParams.set('cursor', String(this.cursor)); 54 + } 55 + 56 + console.log('[jetstream] connecting to', url.toString()); 57 + this.ws = new WebSocket(url.toString()); 58 + 59 + this.ws.onopen = () => { 60 + console.log('[jetstream] connected'); 61 + this.reconnectAttempts = 0; 62 + this.stopPolling(); 63 + this.options.onConnectionChange?.(true); 64 + }; 65 + 66 + this.ws.onmessage = (event) => { 67 + const data = JSON.parse(event.data) as JetstreamEvent; 68 + console.log('[jetstream] message:', data.kind, data.did, (data as any).commit?.operation); 69 + if (data.time_us) { 70 + this.cursor = data.time_us; 71 + } 72 + if ( 73 + data.kind === 'commit' && 74 + (data.commit.operation === 'update' || data.commit.operation === 'create') && 75 + data.commit.collection === COLLECTIONS.game && 76 + data.commit.record 77 + ) { 78 + this.options.onGameUpdate(data.commit.record); 79 + if (!this.opponentRkey) { 80 + this.opponentRkey = data.commit.rkey; 81 + } 82 + } 83 + }; 84 + 85 + this.ws.onclose = (event) => { 86 + console.log('[jetstream] closed:', event.code, event.reason); 87 + this.options.onConnectionChange?.(false); 88 + if (!this.destroyed) { 89 + this.startPolling(); 90 + this.scheduleReconnect(); 91 + } 92 + }; 93 + 94 + this.ws.onerror = (event) => { 95 + console.error('[jetstream] error:', event); 96 + this.ws?.close(); 97 + }; 98 + } 99 + 100 + private scheduleReconnect(): void { 101 + if (this.destroyed) return; 102 + const delay = Math.min( 103 + RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempts), 104 + RECONNECT_MAX_MS 105 + ); 106 + this.reconnectAttempts++; 107 + this.reconnectTimer = setTimeout(() => this.connect(), delay); 108 + } 109 + 110 + startPolling(agent?: Agent, opponentRkey?: string): void { 111 + if (agent) this.agent = agent; 112 + if (opponentRkey) this.opponentRkey = opponentRkey; 113 + if (this.pollTimer || !this.agent || !this.opponentRkey) return; 114 + 115 + this.pollTimer = setInterval(async () => { 116 + if (!this.agent || !this.opponentRkey) return; 117 + const record = await getGame( 118 + this.agent, 119 + this.options.opponentDid, 120 + this.opponentRkey 121 + ); 122 + if (record) { 123 + this.options.onGameUpdate(record as unknown as Record<string, unknown>); 124 + } 125 + }, POLL_INTERVAL_MS); 126 + } 127 + 128 + private stopPolling(): void { 129 + if (this.pollTimer) { 130 + clearInterval(this.pollTimer); 131 + this.pollTimer = null; 132 + } 133 + } 134 + 135 + private cleanup(): void { 136 + if (this.ws) { 137 + this.ws.onclose = null; 138 + this.ws.onerror = null; 139 + this.ws.onmessage = null; 140 + this.ws.close(); 141 + this.ws = null; 142 + } 143 + if (this.reconnectTimer) { 144 + clearTimeout(this.reconnectTimer); 145 + this.reconnectTimer = null; 146 + } 147 + } 148 + 149 + destroy(): void { 150 + this.destroyed = true; 151 + this.cleanup(); 152 + this.stopPolling(); 153 + } 154 + }
+35
src/lib/microcosm.ts
··· 1 + const CONSTELLATION_BASE = 'https://constellation.microcosm.blue'; 2 + const SLINGSHOT_BASE = 'https://slingshot.microcosm.blue'; 3 + 4 + export interface ConstellationLink { 5 + uri: string; 6 + collection: string; 7 + path: string; 8 + target: string; 9 + } 10 + 11 + export interface SlingshotProfile { 12 + did: string; 13 + handle: string; 14 + displayName?: string; 15 + avatar?: string; 16 + } 17 + 18 + export async function findGamesForPlayer(did: string): Promise<ConstellationLink[]> { 19 + const url = new URL(`${CONSTELLATION_BASE}/links/all`); 20 + url.searchParams.set('target', did); 21 + url.searchParams.set('collection', 'blue.checkmate.game'); 22 + 23 + const response = await fetch(url.toString()); 24 + if (!response.ok) return []; 25 + return response.json(); 26 + } 27 + 28 + export async function resolveIdentity(identifier: string): Promise<SlingshotProfile | null> { 29 + const url = new URL(`${SLINGSHOT_BASE}/xrpc/com.bad-example.identity.resolveMiniDoc`); 30 + url.searchParams.set('identifier', identifier); 31 + 32 + const response = await fetch(url.toString()); 33 + if (!response.ok) return null; 34 + return response.json(); 35 + }
+37
src/lib/oauth.ts
··· 1 + import { BrowserOAuthClient } from '@atproto/oauth-client-browser'; 2 + import { buildAtprotoLoopbackClientMetadata } from '@atproto/oauth-types'; 3 + 4 + const SCOPE = 'atproto transition:generic'; 5 + 6 + const isProd = typeof window !== 'undefined' && window.location.hostname === 'checkmate.blue'; 7 + 8 + const prodMetadata = { 9 + client_id: 'https://checkmate.blue/oauth/client-metadata.json' as const, 10 + client_name: 'checkmate.blue', 11 + client_uri: 'https://checkmate.blue' as const, 12 + redirect_uris: ['https://checkmate.blue/oauth/callback'] as const, 13 + scope: SCOPE, 14 + grant_types: ['authorization_code', 'refresh_token'] as const, 15 + response_types: ['code'] as const, 16 + token_endpoint_auth_method: 'none' as const, 17 + application_type: 'web' as const, 18 + dpop_bound_access_tokens: true, 19 + }; 20 + 21 + function devMetadata() { 22 + const port = window.location.port; 23 + const redirectUri = `http://127.0.0.1:${port}/oauth/callback`; 24 + return buildAtprotoLoopbackClientMetadata({ 25 + scope: SCOPE, 26 + redirect_uris: [redirectUri], 27 + }); 28 + } 29 + 30 + export function createOAuthClient(): BrowserOAuthClient { 31 + return new BrowserOAuthClient({ 32 + clientMetadata: isProd ? prodMetadata : devMetadata(), 33 + handleResolver: 'https://bsky.social', 34 + }); 35 + } 36 + 37 + export { SCOPE };
+71
src/lib/stores/auth.svelte.ts
··· 1 + import { Agent } from '@atproto/api'; 2 + import type { BrowserOAuthClient } from '@atproto/oauth-client-browser'; 3 + 4 + const SCOPE = 'atproto transition:generic'; 5 + 6 + let agent: Agent | null = $state(null); 7 + let did: string | undefined = $state(undefined); 8 + let handle: string | undefined = $state(undefined); 9 + let isInitializing: boolean = $state(true); 10 + let oauthClient: BrowserOAuthClient | null = $state(null); 11 + 12 + export const auth = { 13 + get agent() { return agent; }, 14 + get did() { return did; }, 15 + get handle() { return handle; }, 16 + get isInitializing() { return isInitializing; }, 17 + get isLoggedIn() { return agent !== null; }, 18 + 19 + async init(client: BrowserOAuthClient) { 20 + oauthClient = client; 21 + try { 22 + const result = await client.init(); 23 + if (result) { 24 + agent = new Agent(result.session); 25 + did = result.session.did; 26 + await resolveHandle(); 27 + } 28 + } finally { 29 + isInitializing = false; 30 + } 31 + }, 32 + 33 + async signIn(handleOrDid: string) { 34 + if (!oauthClient) throw new Error('OAuth client not initialized'); 35 + // Save current path so we can return after OAuth redirect 36 + sessionStorage.setItem('oauth_return_to', window.location.pathname + window.location.search); 37 + await oauthClient.signIn(handleOrDid, { scope: SCOPE }); 38 + // signIn redirects the browser -- this line is never reached 39 + }, 40 + 41 + getReturnTo(): string { 42 + const returnTo = sessionStorage.getItem('oauth_return_to'); 43 + sessionStorage.removeItem('oauth_return_to'); 44 + return returnTo || '/'; 45 + }, 46 + 47 + async signOut() { 48 + if (oauthClient && did) { 49 + try { await oauthClient.revoke(did); } catch { /* best effort */ } 50 + } 51 + agent = null; 52 + did = undefined; 53 + handle = undefined; 54 + }, 55 + }; 56 + 57 + const PUBLIC_API = 'https://public.api.bsky.app'; 58 + 59 + async function resolveHandle() { 60 + if (!did) return; 61 + try { 62 + const res = await fetch( 63 + `${PUBLIC_API}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}` 64 + ); 65 + if (!res.ok) throw new Error('Profile fetch failed'); 66 + const profile = await res.json(); 67 + handle = profile.handle; 68 + } catch { 69 + handle = did; 70 + } 71 + }
+104
src/lib/stores/game.svelte.ts
··· 1 + import { Chess, type PieceSymbol } from 'chess.js'; 2 + import { 3 + toDests, turnColor, lastMoveSquares, gameResult, isPromotion, applyMove, 4 + type Dests, 5 + } from '$lib/game-logic'; 6 + import type { PlayerColor, GameRecord } from '$lib/types'; 7 + 8 + let chess: Chess = $state(new Chess()); 9 + let myColor: PlayerColor = $state('white'); 10 + let whiteHandle: string | undefined = $state(undefined); 11 + let blackHandle: string | undefined = $state(undefined); 12 + let whiteDid: string | undefined = $state(undefined); 13 + let blackDid: string | undefined = $state(undefined); 14 + let status: GameRecord['status'] = $state('waiting'); 15 + let pendingPromotion: { orig: string; dest: string } | null = $state(null); 16 + 17 + export const game = { 18 + get chess() { return chess; }, 19 + get fen() { return chess.fen(); }, 20 + get pgn() { return chess.pgn(); }, 21 + get turnColor() { return turnColor(chess); }, 22 + get myColor() { return myColor; }, 23 + get isMyTurn() { return turnColor(chess) === myColor && status !== 'completed'; }, 24 + get dests(): Dests { return toDests(chess); }, 25 + get lastMove() { return lastMoveSquares(chess); }, 26 + get result() { return gameResult(chess); }, 27 + get status() { return status; }, 28 + get moveCount() { return chess.history().length; }, 29 + get whiteHandle() { return whiteHandle; }, 30 + get blackHandle() { return blackHandle; }, 31 + get whiteDid() { return whiteDid; }, 32 + get blackDid() { return blackDid; }, 33 + get pendingPromotion() { return pendingPromotion; }, 34 + 35 + init(options: { 36 + pgn?: string; 37 + myColor?: PlayerColor; 38 + whiteHandle?: string; 39 + blackHandle?: string; 40 + whiteDid?: string; 41 + blackDid?: string; 42 + status?: GameRecord['status']; 43 + }) { 44 + chess = new Chess(); 45 + if (options.pgn) { 46 + chess.loadPgn(options.pgn); 47 + } 48 + myColor = options.myColor ?? 'white'; 49 + whiteHandle = options.whiteHandle; 50 + blackHandle = options.blackHandle; 51 + whiteDid = options.whiteDid; 52 + blackDid = options.blackDid; 53 + status = options.status ?? 'active'; 54 + pendingPromotion = null; 55 + }, 56 + 57 + tryMove(orig: string, dest: string): boolean { 58 + if (!this.isMyTurn) return false; 59 + 60 + if (isPromotion(chess, orig, dest)) { 61 + pendingPromotion = { orig, dest }; 62 + return false; 63 + } 64 + 65 + return applyMove(chess, orig, dest); 66 + }, 67 + 68 + completePromotion(piece: PieceSymbol): boolean { 69 + if (!pendingPromotion) return false; 70 + const { orig, dest } = pendingPromotion; 71 + pendingPromotion = null; 72 + return applyMove(chess, orig, dest, piece); 73 + }, 74 + 75 + cancelPromotion() { 76 + pendingPromotion = null; 77 + }, 78 + 79 + applyOpponentMove(pgn: string): boolean { 80 + const newChess = new Chess(); 81 + newChess.loadPgn(pgn); 82 + 83 + if (newChess.history().length > chess.history().length) { 84 + chess = newChess; 85 + return true; 86 + } 87 + return false; 88 + }, 89 + 90 + setStatus(s: GameRecord['status']) { 91 + status = s; 92 + }, 93 + 94 + reset() { 95 + chess = new Chess(); 96 + myColor = 'white'; 97 + whiteHandle = undefined; 98 + blackHandle = undefined; 99 + whiteDid = undefined; 100 + blackDid = undefined; 101 + status = 'waiting'; 102 + pendingPromotion = null; 103 + }, 104 + };
+33
src/lib/types.ts
··· 1 + export interface TimeControl { 2 + type: 'untimed' | 'correspondence' | 'clock'; 3 + initialSeconds?: number; 4 + incrementSeconds?: number; 5 + moveTimeLimitSeconds?: number; 6 + } 7 + 8 + export interface GameRecord { 9 + [key: string]: unknown; 10 + $type: 'blue.checkmate.game'; 11 + pgn: string; 12 + createdAt: string; 13 + white?: string; 14 + black?: string; 15 + status: 'waiting' | 'active' | 'completed' | 'abandoned'; 16 + result?: '1-0' | '0-1' | '1/2-1/2'; 17 + resultReason?: 'checkmate' | 'resignation' | 'draw_agreement' | 'stalemate' | 'insufficient' | 'repetition' | 'fifty_moves'; 18 + parentGameUri?: string; 19 + drawOffered?: boolean; 20 + timeControl?: TimeControl; 21 + moveTimes?: number[]; 22 + } 23 + 24 + export interface ChallengeRecord { 25 + [key: string]: unknown; 26 + $type: 'blue.checkmate.challenge'; 27 + createdAt: string; 28 + opponent?: string; 29 + gameUri?: string; 30 + status: 'open' | 'accepted' | 'expired' | 'cancelled'; 31 + } 32 + 33 + export type PlayerColor = 'white' | 'black';
+38
src/routes/+layout.svelte
··· 1 + <script lang="ts"> 2 + import './layout.css'; 3 + import { onMount } from 'svelte'; 4 + import { auth } from '$lib/stores/auth.svelte'; 5 + import { createOAuthClient } from '$lib/oauth'; 6 + 7 + let { children } = $props(); 8 + 9 + onMount(() => { 10 + const client = createOAuthClient(); 11 + auth.init(client); 12 + }); 13 + </script> 14 + 15 + <svelte:head> 16 + <link rel="icon" href="/favicon.svg" /> 17 + </svelte:head> 18 + 19 + <div class="min-h-screen bg-bg-primary text-text-primary"> 20 + {#if auth.isInitializing} 21 + <div class="flex min-h-screen items-center justify-center"> 22 + <p class="text-text-secondary">Loading...</p> 23 + </div> 24 + {:else} 25 + <nav class="border-b border-border px-4 py-3"> 26 + <div class="mx-auto flex max-w-3xl items-center justify-between"> 27 + <a href="/" class="text-lg font-bold text-accent-blue">checkmate.blue</a> 28 + <div class="flex items-center gap-4"> 29 + {#if auth.isLoggedIn} 30 + <a href="/play" class="text-sm text-text-secondary hover:text-text-primary">New Game</a> 31 + <span class="text-sm text-text-secondary">{auth.handle}</span> 32 + {/if} 33 + </div> 34 + </div> 35 + </nav> 36 + {@render children()} 37 + {/if} 38 + </div>
+1
src/routes/+layout.ts
··· 1 + export const ssr = false;
+85
src/routes/+page.svelte
··· 1 + <script lang="ts"> 2 + import { onMount } from 'svelte'; 3 + import { auth } from '$lib/stores/auth.svelte'; 4 + import { findGamesForPlayer, type ConstellationLink } from '$lib/microcosm'; 5 + import LoginButton from '$lib/components/LoginButton.svelte'; 6 + 7 + let games: ConstellationLink[] = $state([]); 8 + let loadingGames = $state(false); 9 + 10 + $effect(() => { 11 + if (auth.isLoggedIn && auth.did) { 12 + loadGames(auth.did); 13 + } 14 + }); 15 + 16 + async function loadGames(did: string) { 17 + loadingGames = true; 18 + try { 19 + games = await findGamesForPlayer(did); 20 + } catch { 21 + games = []; 22 + } 23 + loadingGames = false; 24 + } 25 + 26 + function parseAtUri(uri: string): { did: string; rkey: string } | null { 27 + const match = uri.match(/^at:\/\/([^/]+)\/[^/]+\/(.+)$/); 28 + if (!match) return null; 29 + return { did: match[1], rkey: match[2] }; 30 + } 31 + </script> 32 + 33 + <div class="flex min-h-screen flex-col items-center justify-center gap-6 p-4"> 34 + <div class="text-center"> 35 + <h1 class="text-4xl font-bold text-accent-blue">checkmate.blue</h1> 36 + <p class="mt-2 text-text-secondary">Chess on the Atmosphere</p> 37 + </div> 38 + 39 + {#if auth.isLoggedIn} 40 + <div class="flex flex-col items-center gap-4"> 41 + <p class="text-lg"> 42 + Signed in as <span class="font-semibold text-accent-blue">{auth.handle}</span> 43 + </p> 44 + <div class="flex gap-3"> 45 + <a 46 + href="/play" 47 + class="rounded-lg bg-accent-blue px-6 py-2 font-semibold text-white transition-colors hover:bg-accent-blue-hover" 48 + > 49 + New Game 50 + </a> 51 + <button 52 + onclick={() => auth.signOut()} 53 + class="rounded-lg border border-border px-6 py-2 text-text-secondary transition-colors hover:text-text-primary" 54 + > 55 + Sign out 56 + </button> 57 + </div> 58 + </div> 59 + 60 + {#if loadingGames} 61 + <p class="text-sm text-text-secondary">Loading games...</p> 62 + {:else if games.length > 0} 63 + <div class="w-full max-w-md"> 64 + <h2 class="mb-3 text-lg font-semibold">Your Games</h2> 65 + <div class="flex flex-col gap-2"> 66 + {#each games as game} 67 + {@const parsed = parseAtUri(game.uri)} 68 + {#if parsed} 69 + <a 70 + href="/game/{parsed.did}/{parsed.rkey}" 71 + class="rounded-lg border border-border bg-bg-secondary px-4 py-3 transition-colors hover:border-accent-blue" 72 + > 73 + <span class="font-mono text-sm text-text-secondary">{parsed.rkey}</span> 74 + </a> 75 + {/if} 76 + {/each} 77 + </div> 78 + </div> 79 + {/if} 80 + {:else} 81 + <div class="w-full max-w-sm"> 82 + <LoginButton /> 83 + </div> 84 + {/if} 85 + </div>
+124
src/routes/challenge/[did]/[rkey]/+page.svelte
··· 1 + <script lang="ts"> 2 + import { page } from '$app/stores'; 3 + import { goto } from '$app/navigation'; 4 + import { onMount } from 'svelte'; 5 + import { auth } from '$lib/stores/auth.svelte'; 6 + import { getChallenge, getGame, createGame } from '$lib/atproto'; 7 + import { resolveIdentity } from '$lib/microcosm'; 8 + import type { ChallengeRecord } from '$lib/types'; 9 + 10 + const challengerDid = $derived($page.params.did); 11 + const rkey = $derived($page.params.rkey); 12 + 13 + let challenge: ChallengeRecord | null = $state(null); 14 + let challengerHandle = $state(''); 15 + let loading = $state(true); 16 + let accepting = $state(false); 17 + let error = $state(''); 18 + 19 + onMount(async () => { 20 + await loadChallenge(); 21 + }); 22 + 23 + async function loadChallenge() { 24 + if (!auth.agent) { 25 + error = 'Sign in to view challenges'; 26 + loading = false; 27 + return; 28 + } 29 + 30 + challenge = await getChallenge(auth.agent, challengerDid, rkey); 31 + if (!challenge) { 32 + error = 'Challenge not found'; 33 + loading = false; 34 + return; 35 + } 36 + 37 + const profile = await resolveIdentity(challengerDid); 38 + challengerHandle = profile?.handle ?? challengerDid; 39 + 40 + loading = false; 41 + } 42 + 43 + async function acceptChallenge() { 44 + if (!auth.agent || !auth.did || !challenge?.gameUri) return; 45 + accepting = true; 46 + error = ''; 47 + 48 + try { 49 + // Parse the game URI to get the owner DID and rkey 50 + const parts = challenge.gameUri.split('/'); 51 + const gameDid = parts[2]; 52 + const gameRkey = parts[parts.length - 1]; 53 + 54 + // Create our own game record 55 + const gameRecord = await getGame(auth.agent, gameDid, gameRkey); 56 + if (!gameRecord) { 57 + error = 'Game not found'; 58 + accepting = false; 59 + return; 60 + } 61 + 62 + const parentUri = `at://${gameDid}/blue.checkmate.game/${gameRkey}`; 63 + await createGame(auth.agent, { 64 + white: gameRecord.white!, 65 + black: auth.did, 66 + status: 'active', 67 + parentGameUri: parentUri, 68 + }); 69 + 70 + // Navigate to the game 71 + goto(`/game/${gameDid}/${gameRkey}`); 72 + } catch (e) { 73 + error = e instanceof Error ? e.message : 'Failed to accept challenge'; 74 + accepting = false; 75 + } 76 + } 77 + </script> 78 + 79 + {#if loading} 80 + <div class="flex min-h-screen items-center justify-center"> 81 + <p class="text-text-secondary">Loading challenge...</p> 82 + </div> 83 + {:else if error} 84 + <div class="flex min-h-screen flex-col items-center justify-center gap-4"> 85 + <p class="text-danger">{error}</p> 86 + <a href="/" class="text-accent-blue hover:underline">Go home</a> 87 + </div> 88 + {:else if challenge} 89 + <div class="flex min-h-screen flex-col items-center justify-center gap-6 p-4"> 90 + <div class="text-center"> 91 + <h1 class="text-3xl font-bold">Chess Challenge</h1> 92 + <p class="mt-2 text-text-secondary"> 93 + <span class="font-semibold text-accent-blue">{challengerHandle}</span> wants to play chess 94 + </p> 95 + </div> 96 + 97 + {#if challenge.status === 'open'} 98 + {#if challengerDid === auth.did} 99 + <div class="text-center"> 100 + <p class="text-text-secondary">Waiting for opponent...</p> 101 + {#if challenge.gameUri} 102 + {@const parts = challenge.gameUri.split('/')} 103 + {@const gameRkey = parts[parts.length - 1]} 104 + <p class="mt-2 text-sm text-text-secondary"> 105 + Share this link: <span class="font-mono text-accent-blue">/game/{challengerDid}/{gameRkey}</span> 106 + </p> 107 + {/if} 108 + </div> 109 + {:else} 110 + <button 111 + onclick={acceptChallenge} 112 + disabled={accepting} 113 + class="rounded-lg bg-accent-blue px-8 py-3 text-lg font-semibold text-white transition-colors hover:bg-accent-blue-hover disabled:opacity-50" 114 + > 115 + {accepting ? 'Accepting...' : 'Accept & Play'} 116 + </button> 117 + {/if} 118 + {:else} 119 + <p class="text-text-secondary">This challenge is {challenge.status}.</p> 120 + {/if} 121 + 122 + <a href="/" class="text-sm text-text-secondary hover:text-text-primary">Back to home</a> 123 + </div> 124 + {/if}
+347
src/routes/game/[did]/[rkey]/+page.svelte
··· 1 + <script lang="ts"> 2 + import { page } from '$app/stores'; 3 + import { onMount } from 'svelte'; 4 + import Board from '$lib/components/Board.svelte'; 5 + import PlayerBar from '$lib/components/PlayerBar.svelte'; 6 + import MoveList from '$lib/components/MoveList.svelte'; 7 + import GameControls from '$lib/components/GameControls.svelte'; 8 + import PromotionModal from '$lib/components/PromotionModal.svelte'; 9 + import { game } from '$lib/stores/game.svelte'; 10 + import { auth } from '$lib/stores/auth.svelte'; 11 + import { getGame, updateGame, createGame, findGameRecordByParent } from '$lib/atproto'; 12 + import { makePgn } from '$lib/game-logic'; 13 + import { JetstreamConnection } from '$lib/jetstream'; 14 + import LoginButton from '$lib/components/LoginButton.svelte'; 15 + import type { PieceSymbol } from 'chess.js'; 16 + 17 + const ownerDid = $derived($page.params.did); 18 + const rkey = $derived($page.params.rkey); 19 + 20 + let myRkey: string | undefined = $state(undefined); 21 + let loading = $state(true); 22 + let error = $state(''); 23 + let loaded = $state(false); 24 + let jsConnection: JetstreamConnection | null = null; 25 + let connected = $state(false); 26 + 27 + onMount(() => { 28 + return () => jsConnection?.destroy(); 29 + }); 30 + 31 + // Load game once auth is ready 32 + $effect(() => { 33 + if (!auth.isInitializing && !loaded) { 34 + if (auth.isLoggedIn) { 35 + loaded = true; 36 + loadGame(); 37 + } else { 38 + loading = false; 39 + } 40 + } 41 + }); 42 + 43 + async function loadGame() { 44 + if (!auth.agent) { 45 + loading = false; 46 + return; 47 + } 48 + 49 + const record = await getGame(auth.agent, ownerDid, rkey); 50 + if (!record) { 51 + error = 'Game not found'; 52 + loading = false; 53 + return; 54 + } 55 + 56 + const isWhite = record.white === auth.did; 57 + const isBlack = record.black === auth.did; 58 + const isOwner = ownerDid === auth.did; 59 + 60 + // Determine my color 61 + let myColor: 'white' | 'black'; 62 + if (isWhite) { 63 + myColor = 'white'; 64 + } else if (isBlack) { 65 + myColor = 'black'; 66 + } else if (record.status === 'waiting' && !record.black) { 67 + // Joining as black 68 + myColor = 'black'; 69 + } else { 70 + // Spectator -- view only 71 + myColor = 'white'; 72 + } 73 + 74 + game.init({ 75 + pgn: record.pgn || undefined, 76 + myColor, 77 + whiteDid: record.white, 78 + blackDid: record.black, 79 + status: record.status === 'waiting' && myColor === 'white' ? 'waiting' : record.status, 80 + }); 81 + 82 + // Find the paired (Black's) record and reconcile state. 83 + // Each player's record is one move behind 50% of the time, 84 + // so we read both and use the longer PGN. 85 + const parentUri = `at://${ownerDid}/blue.checkmate.game/${rkey}`; 86 + let blackDid = record.black; 87 + 88 + if (isOwner) { 89 + // I'm White -- find Black's record for latest state 90 + myRkey = rkey; 91 + if (blackDid) { 92 + const blackResult = await findGameRecordByParent(auth.agent, blackDid, parentUri); 93 + if (blackResult?.record.pgn) { 94 + game.applyOpponentMove(blackResult.record.pgn); 95 + } 96 + } 97 + } else if (myColor === 'black') { 98 + // I'm Black -- find my own record (also has potentially newer PGN) 99 + const myResult = await findGameRecordByParent(auth.agent, auth.did!, parentUri); 100 + if (myResult) { 101 + myRkey = myResult.rkey; 102 + if (myResult.record.pgn) { 103 + game.applyOpponentMove(myResult.record.pgn); 104 + } 105 + } else { 106 + await joinAsBlack(record); 107 + } 108 + } 109 + 110 + // Connect Jetstream for opponent moves 111 + if (myColor === 'white') { 112 + if (blackDid && game.status === 'active') { 113 + connectJetstream(blackDid); 114 + } else { 115 + waitForOpponent(); 116 + } 117 + } else if (myColor === 'black' && record.white) { 118 + connectJetstream(record.white); 119 + } 120 + 121 + loading = false; 122 + } 123 + 124 + function waitForOpponent() { 125 + // Listen to Jetstream for any blue.checkmate.game creates 126 + // that reference our DID (someone joining our game) 127 + jsConnection?.destroy(); 128 + jsConnection = new JetstreamConnection({ 129 + opponentDid: '', // empty = no DID filter 130 + onGameUpdate: async (record) => { 131 + const white = record.white as string; 132 + const black = record.black as string; 133 + // Someone created a game record where we're white 134 + if (white === auth.did && black && black !== auth.did) { 135 + // Found our opponent! 136 + game.setStatus('active'); 137 + game.init({ 138 + pgn: (record.pgn as string) || undefined, 139 + myColor: 'white' as const, 140 + whiteDid: white, 141 + blackDid: black, 142 + status: 'active' as const, 143 + }); 144 + // Persist Black's DID and active status to our record so it survives reload 145 + if (auth.agent && myRkey) { 146 + await updateGame(auth.agent, myRkey, { black, status: 'active' }); 147 + } 148 + // Reconnect Jetstream filtered to opponent 149 + connectJetstream(black); 150 + } 151 + }, 152 + onConnectionChange: (isConnected) => { 153 + connected = isConnected; 154 + }, 155 + }); 156 + jsConnection.connect(); 157 + } 158 + 159 + function connectJetstream(opponentDid: string) { 160 + jsConnection?.destroy(); 161 + jsConnection = new JetstreamConnection({ 162 + opponentDid, 163 + onGameUpdate: (record) => { 164 + const pgn = record.pgn as string; 165 + if (pgn) { 166 + const applied = game.applyOpponentMove(pgn); 167 + console.log('[game] opponent update:', applied ? 'new move applied' : 'no new moves'); 168 + 169 + // Check if opponent resigned or game ended 170 + const status = record.status as string; 171 + if (status === 'completed') { 172 + game.setStatus('completed'); 173 + } 174 + } 175 + }, 176 + onConnectionChange: (isConnected) => { 177 + connected = isConnected; 178 + }, 179 + }); 180 + jsConnection.connect(); 181 + } 182 + 183 + async function joinAsBlack(record: any) { 184 + if (!auth.agent || !auth.did) return; 185 + 186 + const parentUri = `at://${ownerDid}/blue.checkmate.game/${rkey}`; 187 + const result = await createGame(auth.agent, { 188 + white: record.white, 189 + black: auth.did, 190 + status: 'active', 191 + parentGameUri: parentUri, 192 + }); 193 + myRkey = result.rkey; 194 + game.setStatus('active'); 195 + } 196 + 197 + async function handleMove(orig: string, dest: string) { 198 + const moved = game.tryMove(orig, dest); 199 + if (moved) { 200 + await writeMove(); 201 + } 202 + } 203 + 204 + async function handlePromotion(piece: PieceSymbol) { 205 + const moved = game.completePromotion(piece); 206 + if (moved) { 207 + await writeMove(); 208 + } 209 + } 210 + 211 + async function writeMove() { 212 + if (!auth.agent || !auth.did || !myRkey) return; 213 + 214 + await updateGame(auth.agent, myRkey, { 215 + pgn: makePgn(game.chess, game.whiteDid, game.blackDid), 216 + status: game.result ? 'completed' : 'active', 217 + result: game.result?.result, 218 + resultReason: game.result?.reason as any, 219 + }); 220 + } 221 + 222 + async function handleResign() { 223 + if (!auth.agent || !auth.did || !myRkey) return; 224 + 225 + const result = game.myColor === 'white' ? '0-1' : '1-0'; 226 + game.setStatus('completed'); 227 + await updateGame(auth.agent, myRkey, { 228 + status: 'completed', 229 + result: result as any, 230 + resultReason: 'resignation', 231 + }); 232 + } 233 + 234 + async function handleDrawOffer() { 235 + if (!auth.agent || !auth.did || !myRkey) return; 236 + await updateGame(auth.agent, myRkey, { drawOffered: true }); 237 + } 238 + 239 + let copied = $state(false); 240 + 241 + function copyGameLink() { 242 + const url = `${window.location.origin}/game/${ownerDid}/${rkey}`; 243 + navigator.clipboard.writeText(url); 244 + copied = true; 245 + setTimeout(() => copied = false, 2000); 246 + } 247 + 248 + const isWaiting = $derived(game.status === 'waiting'); 249 + 250 + const opponentHandle = $derived( 251 + game.myColor === 'white' ? game.blackHandle : game.whiteHandle 252 + ); 253 + const myHandle = $derived( 254 + game.myColor === 'white' ? game.whiteHandle : game.blackHandle 255 + ); 256 + </script> 257 + 258 + {#if loading} 259 + <div class="flex min-h-screen items-center justify-center"> 260 + <p class="text-text-secondary">Loading game...</p> 261 + </div> 262 + {:else if !auth.isLoggedIn} 263 + <div class="flex min-h-screen flex-col items-center justify-center gap-6 p-4"> 264 + <div class="text-center"> 265 + <h1 class="text-2xl font-bold">You've been challenged!</h1> 266 + <p class="mt-2 text-text-secondary">Sign in with your Bluesky account to play.</p> 267 + </div> 268 + <div class="w-full max-w-sm"> 269 + <LoginButton /> 270 + </div> 271 + </div> 272 + {:else if error} 273 + <div class="flex min-h-screen items-center justify-center"> 274 + <p class="text-danger">{error}</p> 275 + </div> 276 + {:else} 277 + <div class="flex min-h-screen flex-col items-center justify-center gap-4 p-4"> 278 + {#if isWaiting} 279 + <div class="w-full max-w-md rounded-lg border border-border bg-bg-secondary p-6 text-center"> 280 + <p class="text-lg font-semibold">Waiting for opponent</p> 281 + <p class="mt-2 text-sm text-text-secondary">Share this link to invite someone:</p> 282 + <div class="mt-3 flex items-center gap-2"> 283 + <input 284 + type="text" 285 + readonly 286 + value="{window.location.origin}/game/{ownerDid}/{rkey}" 287 + class="flex-1 rounded-lg border border-border bg-bg-primary px-3 py-2 font-mono text-xs text-text-primary" 288 + /> 289 + <button 290 + onclick={copyGameLink} 291 + class="rounded-lg bg-accent-blue px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-accent-blue-hover" 292 + > 293 + {copied ? 'Copied!' : 'Copy'} 294 + </button> 295 + </div> 296 + </div> 297 + {/if} 298 + 299 + <PlayerBar handle={opponentHandle ?? 'Waiting...'} isActive={!game.isMyTurn && game.status === 'active'} /> 300 + 301 + <Board 302 + fen={game.fen} 303 + orientation={game.myColor} 304 + turnColor={game.turnColor} 305 + dests={game.isMyTurn ? game.dests : undefined} 306 + lastMove={game.lastMove} 307 + viewOnly={game.status === 'completed'} 308 + onmove={handleMove} 309 + /> 310 + 311 + <PlayerBar handle={myHandle ?? 'You'} isActive={game.isMyTurn} /> 312 + 313 + {#if game.result} 314 + <div class="rounded-lg bg-bg-secondary p-4 text-center"> 315 + <p class="text-lg font-bold"> 316 + {#if game.result.result === '1-0'} 317 + White wins 318 + {:else if game.result.result === '0-1'} 319 + Black wins 320 + {:else} 321 + Draw 322 + {/if} 323 + </p> 324 + <p class="text-sm text-text-secondary">{game.result.reason}</p> 325 + </div> 326 + {/if} 327 + 328 + <GameControls 329 + gameOver={game.result !== null || game.status === 'completed'} 330 + onresign={handleResign} 331 + ondrawoffer={handleDrawOffer} 332 + /> 333 + 334 + <MoveList chess={game.chess} /> 335 + 336 + {#if auth.isLoggedIn} 337 + <div class="flex items-center gap-2 text-xs text-text-secondary"> 338 + <span class="inline-block h-2 w-2 rounded-full" class:bg-success={connected} class:bg-danger={!connected}></span> 339 + {connected ? 'Connected' : 'Reconnecting...'} 340 + </div> 341 + {/if} 342 + </div> 343 + 344 + {#if game.pendingPromotion} 345 + <PromotionModal color={game.myColor} onselect={handlePromotion} /> 346 + {/if} 347 + {/if}
+26
src/routes/layout.css
··· 1 + @import 'tailwindcss'; 2 + @import '@lichess-org/chessground/assets/chessground.base.css'; 3 + @import '@lichess-org/chessground/assets/chessground.brown.css'; 4 + @import '@lichess-org/chessground/assets/chessground.cburnett.css'; 5 + 6 + @theme { 7 + --color-bg-primary: #0f1419; 8 + --color-bg-secondary: #1a2332; 9 + --color-bg-board: #2a3a4a; 10 + --color-accent-blue: #1d9bf0; 11 + --color-accent-blue-hover: #1a8cd8; 12 + --color-text-primary: #e7e9ea; 13 + --color-text-secondary: #71767b; 14 + --color-success: #00ba7c; 15 + --color-danger: #f4212e; 16 + --color-warning: #ffd400; 17 + --color-border: #2f3336; 18 + 19 + --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace; 20 + } 21 + 22 + body { 23 + background-color: var(--color-bg-primary); 24 + color: var(--color-text-primary); 25 + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 26 + }
+14
src/routes/oauth/callback/+page.svelte
··· 1 + <script lang="ts"> 2 + import { goto } from '$app/navigation'; 3 + import { auth } from '$lib/stores/auth.svelte'; 4 + 5 + $effect(() => { 6 + if (!auth.isInitializing && auth.isLoggedIn) { 7 + goto(auth.getReturnTo()); 8 + } 9 + }); 10 + </script> 11 + 12 + <div class="flex min-h-screen items-center justify-center"> 13 + <p class="text-text-secondary">Completing sign in...</p> 14 + </div>
+18
src/routes/oauth/client-metadata.json/+server.ts
··· 1 + import { json } from '@sveltejs/kit'; 2 + 3 + export const prerender = true; 4 + 5 + export function GET() { 6 + return json({ 7 + client_id: 'https://checkmate.blue/oauth/client-metadata.json', 8 + client_name: 'checkmate.blue', 9 + client_uri: 'https://checkmate.blue', 10 + redirect_uris: ['https://checkmate.blue/oauth/callback'], 11 + scope: 'atproto transition:generic', 12 + grant_types: ['authorization_code', 'refresh_token'], 13 + response_types: ['code'], 14 + token_endpoint_auth_method: 'none', 15 + application_type: 'web', 16 + dpop_bound_access_tokens: true 17 + }); 18 + }
+89
src/routes/play/+page.svelte
··· 1 + <script lang="ts"> 2 + import { goto } from '$app/navigation'; 3 + import { auth } from '$lib/stores/auth.svelte'; 4 + import { createGame, createChallenge, updateChallenge } from '$lib/atproto'; 5 + import { resolveIdentity } from '$lib/microcosm'; 6 + 7 + let opponentHandle = $state(''); 8 + let isCreating = $state(false); 9 + let error = $state(''); 10 + 11 + async function handleCreateGame() { 12 + if (!auth.agent || !auth.did) return; 13 + isCreating = true; 14 + error = ''; 15 + 16 + try { 17 + // Resolve opponent if specified 18 + let opponentDid: string | undefined; 19 + if (opponentHandle.trim()) { 20 + const profile = await resolveIdentity(opponentHandle.trim()); 21 + if (!profile) { 22 + error = 'Could not resolve that handle'; 23 + isCreating = false; 24 + return; 25 + } 26 + opponentDid = profile.did; 27 + } 28 + 29 + // Create the game record (status: waiting) 30 + const gameResult = await createGame(auth.agent, { 31 + white: auth.did, 32 + black: opponentDid, 33 + status: opponentDid ? 'waiting' : 'waiting', 34 + }); 35 + 36 + // Create a challenge record pointing to the game 37 + const challengeResult = await createChallenge(auth.agent, { 38 + opponent: opponentDid, 39 + }); 40 + 41 + // Update challenge with game URI 42 + await updateChallenge(auth.agent, challengeResult.rkey, { 43 + gameUri: gameResult.uri, 44 + status: 'open', 45 + }); 46 + 47 + // Navigate to the game page 48 + goto(`/game/${auth.did}/${gameResult.rkey}`); 49 + } catch (e) { 50 + error = e instanceof Error ? e.message : 'Failed to create game'; 51 + isCreating = false; 52 + } 53 + } 54 + </script> 55 + 56 + <div class="flex min-h-screen flex-col items-center justify-center gap-8 p-4"> 57 + <div class="text-center"> 58 + <h1 class="text-3xl font-bold">New Game</h1> 59 + <p class="mt-1 text-text-secondary">Challenge someone or create an open game</p> 60 + </div> 61 + 62 + {#if !auth.isLoggedIn} 63 + <p class="text-text-secondary">Sign in to create a game.</p> 64 + <a href="/" class="text-accent-blue hover:underline">Go to home</a> 65 + {:else} 66 + <form onsubmit={handleCreateGame} class="flex w-full max-w-sm flex-col gap-4"> 67 + <input 68 + type="text" 69 + bind:value={opponentHandle} 70 + placeholder="Opponent handle (optional)" 71 + class="rounded-lg border border-border bg-bg-secondary px-4 py-2 text-text-primary placeholder:text-text-secondary focus:border-accent-blue focus:outline-none" 72 + /> 73 + 74 + <button 75 + type="submit" 76 + disabled={isCreating} 77 + class="rounded-lg bg-accent-blue px-6 py-3 font-semibold text-white transition-colors hover:bg-accent-blue-hover disabled:opacity-50" 78 + > 79 + {isCreating ? 'Creating...' : opponentHandle.trim() ? 'Challenge Player' : 'Create Open Game'} 80 + </button> 81 + 82 + {#if error} 83 + <p class="text-sm text-danger">{error}</p> 84 + {/if} 85 + </form> 86 + 87 + <a href="/" class="text-sm text-text-secondary hover:text-text-primary">Back to home</a> 88 + {/if} 89 + </div>
+74
src/routes/profile/[handle]/+page.svelte
··· 1 + <script lang="ts"> 2 + import { page } from '$app/stores'; 3 + import { onMount } from 'svelte'; 4 + import { resolveIdentity, findGamesForPlayer, type ConstellationLink, type SlingshotProfile } from '$lib/microcosm'; 5 + 6 + const handle = $derived($page.params.handle); 7 + 8 + let profile: SlingshotProfile | null = $state(null); 9 + let games: ConstellationLink[] = $state([]); 10 + let loading = $state(true); 11 + let error = $state(''); 12 + 13 + onMount(async () => { 14 + const resolved = await resolveIdentity(handle); 15 + if (!resolved) { 16 + error = 'Player not found'; 17 + loading = false; 18 + return; 19 + } 20 + profile = resolved; 21 + games = await findGamesForPlayer(resolved.did); 22 + loading = false; 23 + }); 24 + 25 + function parseAtUri(uri: string): { did: string; rkey: string } | null { 26 + const match = uri.match(/^at:\/\/([^/]+)\/[^/]+\/(.+)$/); 27 + if (!match) return null; 28 + return { did: match[1], rkey: match[2] }; 29 + } 30 + </script> 31 + 32 + {#if loading} 33 + <div class="flex min-h-screen items-center justify-center"> 34 + <p class="text-text-secondary">Loading profile...</p> 35 + </div> 36 + {:else if error} 37 + <div class="flex min-h-screen flex-col items-center justify-center gap-4"> 38 + <p class="text-danger">{error}</p> 39 + <a href="/" class="text-accent-blue hover:underline">Go home</a> 40 + </div> 41 + {:else if profile} 42 + <div class="flex min-h-screen flex-col items-center gap-8 p-4 pt-16"> 43 + <div class="text-center"> 44 + <div class="mx-auto flex h-20 w-20 items-center justify-center rounded-full bg-bg-secondary text-3xl font-bold"> 45 + {profile.displayName?.[0]?.toUpperCase() ?? profile.handle[0]?.toUpperCase() ?? '?'} 46 + </div> 47 + {#if profile.displayName} 48 + <h1 class="mt-3 text-2xl font-bold">{profile.displayName}</h1> 49 + {/if} 50 + <p class="mt-1 text-text-secondary">@{profile.handle}</p> 51 + </div> 52 + 53 + {#if games.length > 0} 54 + <div class="w-full max-w-md"> 55 + <h2 class="mb-3 text-lg font-semibold">Games ({games.length})</h2> 56 + <div class="flex flex-col gap-2"> 57 + {#each games as game} 58 + {@const parsed = parseAtUri(game.uri)} 59 + {#if parsed} 60 + <a 61 + href="/game/{parsed.did}/{parsed.rkey}" 62 + class="rounded-lg border border-border bg-bg-secondary px-4 py-3 transition-colors hover:border-accent-blue" 63 + > 64 + <span class="font-mono text-sm text-text-secondary">{parsed.rkey}</span> 65 + </a> 66 + {/if} 67 + {/each} 68 + </div> 69 + </div> 70 + {:else} 71 + <p class="text-text-secondary">No games yet.</p> 72 + {/if} 73 + </div> 74 + {/if}
+4
static/favicon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"> 2 + <rect width="32" height="32" rx="4" fill="#1d9bf0"/> 3 + <text x="16" y="24" font-size="22" text-anchor="middle" fill="white">&#9816;</text> 4 + </svg>
+3
static/robots.txt
··· 1 + # allow crawling everything by default 2 + User-agent: * 3 + Disallow:
+23
svelte.config.js
··· 1 + import adapter from '@sveltejs/adapter-static'; 2 + import { relative, sep } from 'node:path'; 3 + 4 + /** @type {import('@sveltejs/kit').Config} */ 5 + const config = { 6 + compilerOptions: { 7 + // defaults to rune mode for the project, execept for `node_modules`. Can be removed in svelte 6. 8 + runes: ({ filename }) => { 9 + const relativePath = relative(import.meta.dirname, filename); 10 + const pathSegments = relativePath.toLowerCase().split(sep); 11 + const isExternalLibrary = pathSegments.includes('node_modules'); 12 + 13 + return isExternalLibrary ? undefined : true; 14 + } 15 + }, 16 + kit: { 17 + adapter: adapter({ 18 + fallback: 'index.html' 19 + }) 20 + } 21 + }; 22 + 23 + export default config;
+20
tsconfig.json
··· 1 + { 2 + "extends": "./.svelte-kit/tsconfig.json", 3 + "compilerOptions": { 4 + "rewriteRelativeImportExtensions": true, 5 + "allowJs": true, 6 + "checkJs": true, 7 + "esModuleInterop": true, 8 + "forceConsistentCasingInFileNames": true, 9 + "resolveJsonModule": true, 10 + "skipLibCheck": true, 11 + "sourceMap": true, 12 + "strict": true, 13 + "moduleResolution": "bundler" 14 + } 15 + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 16 + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 17 + // 18 + // To make changes to top-level options such as include and exclude, we recommend extending 19 + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript 20 + }
+11
vite.config.ts
··· 1 + import tailwindcss from '@tailwindcss/vite'; 2 + import { sveltekit } from '@sveltejs/kit/vite'; 3 + import { defineConfig } from 'vite'; 4 + 5 + export default defineConfig({ 6 + plugins: [tailwindcss(), sveltekit()], 7 + server: { 8 + host: '127.0.0.1', 9 + port: 5173 10 + } 11 + });