From f2819ead6d5af2e8be679b8bbc95d25702204d6a Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 25 Jul 2026 10:54:36 +0700 Subject: [PATCH 1/5] test: add characterization tests for lib layer Cover classic-mode comparison rules, game-engine state machine and localStorage persistence, and champion-data search/seeding/loading with Vitest. Storage keys are asserted literally so returning players' saved games and stats cannot silently break. Node environment with a hand-rolled localStorage stub; fixtures are four hand-written champions so the weekly data sync cannot destabilise the suite. --- package.json | 7 +- pnpm-lock.yaml | 543 ++++++++++++++++++++++++++++++++++++ test/champion-data.test.js | 196 +++++++++++++ test/classic-mode.test.js | 163 +++++++++++ test/fixtures/champions.js | 66 +++++ test/game-engine.test.js | 357 ++++++++++++++++++++++++ test/setup-local-storage.js | 37 +++ vitest.config.js | 9 + 8 files changed, 1376 insertions(+), 2 deletions(-) create mode 100644 test/champion-data.test.js create mode 100644 test/classic-mode.test.js create mode 100644 test/fixtures/champions.js create mode 100644 test/game-engine.test.js create mode 100644 test/setup-local-storage.js create mode 100644 vitest.config.js diff --git a/package.json b/package.json index e2bce5d..bc2b25f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "test": "vitest run", + "test:watch": "vitest" }, "dependencies": { "next": "16.2.10", @@ -18,6 +20,7 @@ "@tailwindcss/postcss": "^4.3.3", "eslint": "^9", "eslint-config-next": "16.2.10", - "tailwindcss": "^4.3.3" + "tailwindcss": "^4.3.3", + "vitest": "^4.1.10" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1375759..65bb00b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: tailwindcss: specifier: ^4.3.3 version: 4.3.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(vite@8.1.5(jiti@2.7.0)) packages: @@ -129,12 +132,21 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -368,6 +380,12 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@next/env@16.2.10': resolution: {integrity: sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==} @@ -442,9 +460,113 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -543,6 +665,15 @@ packages: '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -731,6 +862,35 @@ packages: cpu: [x64] os: [win32] + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -787,6 +947,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -853,6 +1017,10 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -958,6 +1126,9 @@ packages: resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} engines: {node: '>= 0.4'} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -1098,10 +1269,17 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1150,6 +1328,11 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1618,6 +1801,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1649,6 +1836,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1660,6 +1850,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -1718,6 +1912,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1785,6 +1984,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1792,6 +1994,12 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -1855,10 +2063,25 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1923,6 +2146,90 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -1944,6 +2251,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2087,16 +2399,32 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@2.7.0))': dependencies: eslint: 9.39.5(jiti@2.7.0) @@ -2282,6 +2610,13 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + '@next/env@16.2.10': {} '@next/eslint-plugin-next@16.2.10': @@ -2326,8 +2661,63 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@oxc-project/types@0.139.0': {} + + '@rolldown/binding-android-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-x64@1.1.5': + optional: true + + '@rolldown/binding-freebsd-x64@1.1.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.5': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.5': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + '@rtsao/scc@1.1.0': {} + '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -2406,6 +2796,18 @@ snapshots: tslib: 2.8.1 optional: true + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} @@ -2573,6 +2975,47 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.10(vite@8.1.5(jiti@2.7.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.5(jiti@2.7.0) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -2661,6 +3104,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-function@1.0.0: {} @@ -2721,6 +3166,8 @@ snapshots: caniuse-lite@1.0.30001793: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2887,6 +3334,8 @@ snapshots: iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 + es-module-lexer@2.3.1: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -3115,8 +3564,14 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} + expect-type@1.4.0: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -3163,6 +3618,9 @@ snapshots: dependencies: is-callable: 1.2.7 + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -3612,6 +4070,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + obug@2.1.4: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3645,12 +4105,16 @@ snapshots: path-parse@1.0.7: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} picomatch@4.0.4: {} + picomatch@4.0.5: {} + possible-typed-array-names@1.1.0: {} postcss@8.5.19: @@ -3715,6 +4179,27 @@ snapshots: reusify@1.1.0: {} + rolldown@1.1.5: + dependencies: + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3832,10 +4317,16 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + source-map-js@1.2.1: {} stable-hash@0.0.5: {} + stackback@0.0.2: {} + + std-env@4.2.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -3912,11 +4403,22 @@ snapshots: tapable@2.3.3: {} + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -4028,6 +4530,42 @@ snapshots: dependencies: punycode: 2.3.1 + vite@8.1.5(jiti@2.7.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.19 + rolldown: 1.1.5 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + jiti: 2.7.0 + + vitest@4.1.10(vite@8.1.5(jiti@2.7.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.1.5(jiti@2.7.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.1.5(jiti@2.7.0) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - msw + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -4073,6 +4611,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} yallist@3.1.1: {} diff --git a/test/champion-data.test.js b/test/champion-data.test.js new file mode 100644 index 0000000..b4d01f6 --- /dev/null +++ b/test/champion-data.test.js @@ -0,0 +1,196 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { getChampionImageUrl, getTodaySeed } from "../lib/champion-data"; +import { AATROX, CHAMPIONS } from "./fixtures/champions"; + +/** + * `champions` is module-private with no reset export, so each group gets a fresh + * module instance instead of adding production code for testability. + */ +async function freshModule({ champions = CHAMPIONS, ok = true, status = 200 } = {}) { + vi.resetModules(); + const fetchMock = vi.fn(async () => ({ + ok, + status, + json: async () => champions, + })); + vi.stubGlobal("fetch", fetchMock); + const mod = await import("../lib/champion-data"); + return { mod, fetchMock }; +} + +/** Load a module instance with its champion list already populated. */ +async function loadedModule(champions = CHAMPIONS) { + const { mod, fetchMock } = await freshModule({ champions }); + await mod.loadChampions(); + return { mod, fetchMock }; +} + +function named(names) { + return names.map((name) => ({ ...AATROX, id: name, name })); +} + +describe("loadChampions", () => { + it("fetches the static champions file and returns the parsed list", async () => { + const { mod, fetchMock } = await freshModule(); + await expect(mod.loadChampions()).resolves.toEqual(CHAMPIONS); + expect(fetchMock).toHaveBeenCalledWith("/champions.json"); + }); + + it("serves later calls from the cache without refetching", async () => { + const { mod, fetchMock } = await loadedModule(); + await mod.loadChampions(); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + it("is single-flight — concurrent callers share one fetch", async () => { + const { mod, fetchMock } = await freshModule(); + const [first, second] = await Promise.all([ + mod.loadChampions(), + mod.loadChampions(), + ]); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(first).toBe(second); + }); + + it("rejects with the status code when the response is not ok", async () => { + const { mod } = await freshModule({ ok: false, status: 404 }); + await expect(mod.loadChampions()).rejects.toThrow( + "Failed to load champions: 404", + ); + }); + + it("clears the in-flight promise on failure so a retry can succeed", async () => { + const { mod, fetchMock } = await freshModule({ ok: false, status: 500 }); + await expect(mod.loadChampions()).rejects.toThrow(); + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => CHAMPIONS, + }); + + await expect(mod.loadChampions()).resolves.toEqual(CHAMPIONS); + expect(fetchMock).toHaveBeenCalledTimes(2); + }); +}); + +describe("searchChampions", () => { + it("returns nothing for an empty query", async () => { + const { mod } = await loadedModule(); + expect(mod.searchChampions("")).toEqual([]); + }); + + it("matches case-insensitively on a substring", async () => { + const { mod } = await loadedModule(); + expect(mod.searchChampions("AATR").map((c) => c.name)).toEqual(["Aatrox"]); + }); + + it("sorts prefix matches ahead of substring matches", async () => { + const { mod } = await loadedModule(named(["Akali", "Kayle", "Kayn"])); + // "Kayle"/"Kayn" start with "ka"; "Akali" only contains it + expect(mod.searchChampions("ka").map((c) => c.name)).toEqual([ + "Kayle", + "Kayn", + "Akali", + ]); + }); + + it("sorts alphabetically within each tier", async () => { + const { mod } = await loadedModule(named(["Azir", "Ahri", "Aatrox"])); + expect(mod.searchChampions("a").map((c) => c.name)).toEqual([ + "Aatrox", + "Ahri", + "Azir", + ]); + }); + + it("caps the result at 8", async () => { + const many = named([ + "Aatrox", "Ahri", "Akali", "Alistar", "Amumu", + "Anivia", "Annie", "Aphelios", "Ashe", "Azir", + ]); + const { mod } = await loadedModule(many); + expect(mod.searchChampions("a")).toHaveLength(8); + }); + + it("filters excluded names case-insensitively", async () => { + const { mod } = await loadedModule(named(["Kayle", "Kayn"])); + expect(mod.searchChampions("ka", ["kAyN"]).map((c) => c.name)).toEqual([ + "Kayle", + ]); + }); + + it("returns an empty list when nothing matches", async () => { + const { mod } = await loadedModule(); + expect(mod.searchChampions("zzzz")).toEqual([]); + }); +}); + +describe("getRandomChampion", () => { + it("is deterministic for a given seed", async () => { + const { mod } = await loadedModule(); + expect(mod.getRandomChampion("2026-07-25")).toBe( + mod.getRandomChampion("2026-07-25"), + ); + }); + + it("always returns a champion from the loaded list", async () => { + const { mod } = await loadedModule(); + for (const seed of ["a", "b", "2026-07-25", "unlimited_1_0.5", ""]) { + expect(CHAMPIONS).toContain(mod.getRandomChampion(seed)); + } + }); + + it("returns null before champions are loaded", async () => { + const { mod } = await freshModule(); + expect(mod.getRandomChampion("2026-07-25")).toBeNull(); + }); +}); + +describe("getTodaySeed", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("formats as YYYY-MM-DD", () => { + vi.setSystemTime(new Date("2026-07-25T09:00:00Z")); + expect(getTodaySeed()).toMatch(/^\d{4}-\d{2}-\d{2}$/); + }); + + it("reflects the local date, not the UTC date", () => { + // 18:00 UTC is already the next day in UTC+7 and still the same day in UTC, + // so this instant fails any implementation that reaches for UTC getters. + vi.setSystemTime(new Date("2026-07-25T18:00:00Z")); + // en-CA renders as YYYY-MM-DD — an independent derivation of the same date. + expect(getTodaySeed()).toBe(new Date().toLocaleDateString("en-CA")); + }); + + it("zero-pads single-digit months and days", () => { + vi.setSystemTime(new Date(2026, 0, 5, 12, 0, 0)); + expect(getTodaySeed()).toBe("2026-01-05"); + }); + + it("advances by one day across a local midnight", () => { + vi.setSystemTime(new Date(2026, 6, 25, 23, 59, 0)); + const before = getTodaySeed(); + vi.setSystemTime(new Date(2026, 6, 26, 0, 1, 0)); + expect(getTodaySeed()).not.toBe(before); + expect(getTodaySeed()).toBe("2026-07-26"); + }); +}); + +describe("getChampionImageUrl", () => { + it("returns the absolute Data Dragon tile URL", () => { + expect(getChampionImageUrl("Aatrox")).toBe( + "https://ddragon.leagueoflegends.com/cdn/img/champion/tiles/Aatrox_0.jpg", + ); + }); + + it("stays absolute — it must never be rewritten through a base path", () => { + expect(getChampionImageUrl("Belveth")).toMatch(/^https:\/\//); + }); +}); diff --git a/test/classic-mode.test.js b/test/classic-mode.test.js new file mode 100644 index 0000000..b3df281 --- /dev/null +++ b/test/classic-mode.test.js @@ -0,0 +1,163 @@ +import { describe, expect, it } from "vitest"; +import { CLASSIC_ATTRIBUTES, compareChampions } from "../lib/classic-mode"; +import { AATROX, AHRI, AKALI, BELVETH } from "./fixtures/champions"; + +/** Pull one attribute's comparison out of the 7-entry result array. */ +function cell(guess, target, key) { + return compareChampions(guess, target).find((r) => r.key === key); +} + +describe("CLASSIC_ATTRIBUTES", () => { + it("exposes 7 attributes with gender first and releaseDate last", () => { + expect(CLASSIC_ATTRIBUTES).toHaveLength(7); + expect(CLASSIC_ATTRIBUTES[0].key).toBe("gender"); + expect(CLASSIC_ATTRIBUTES[6].key).toBe("releaseDate"); + }); + + it("keeps the display order the grid header depends on", () => { + expect(CLASSIC_ATTRIBUTES.map((a) => a.key)).toEqual([ + "gender", + "genre", + "attackType", + "resource", + "region", + "lane", + "releaseDate", + ]); + }); +}); + +describe("compareChampions — result shape", () => { + it("returns one entry per attribute, carrying label and type through", () => { + const results = compareChampions(AHRI, AATROX); + expect(results).toHaveLength(7); + expect(results[0]).toMatchObject({ + key: "gender", + label: "Gender", + type: "exact", + }); + }); +}); + +describe("compareChampions — exact", () => { + it("marks an identical value correct", () => { + expect(cell(AKALI, AHRI, "gender").result).toBe("correct"); + }); + + it("marks a differing value wrong", () => { + expect(cell(AHRI, AATROX, "gender").result).toBe("wrong"); + }); + + it("compares case-insensitively", () => { + const shouty = { ...AATROX, gender: "MALE" }; + expect(cell(shouty, AATROX, "gender").result).toBe("correct"); + }); + + it("treats a missing value as not matching a present one", () => { + expect(cell(BELVETH, AATROX, "resource").result).toBe("wrong"); + }); +}); + +describe("compareChampions — multi", () => { + it("marks identical sets correct", () => { + expect(cell(BELVETH, AATROX, "genre").result).toBe("correct"); + }); + + it("marks intersecting sets partial", () => { + // AKALI lane "top,mid" overlaps AATROX lane "top" + expect(cell(AKALI, AATROX, "lane").result).toBe("partial"); + }); + + it("marks disjoint sets wrong", () => { + expect(cell(AHRI, AATROX, "lane").result).toBe("wrong"); + }); + + it("marks two empty sets correct", () => { + const a = { ...AATROX, lane: "" }; + const b = { ...AATROX, lane: "" }; + expect(cell(a, b, "lane").result).toBe("correct"); + }); + + it("marks one empty set against a populated one wrong", () => { + const empty = { ...AATROX, lane: "" }; + expect(cell(empty, AATROX, "lane").result).toBe("wrong"); + }); + + it("ignores order and whitespace when comparing sets", () => { + const spaced = { ...AKALI, lane: " mid , top " }; + expect(cell(spaced, AKALI, "lane").result).toBe("correct"); + }); +}); + +describe("compareChampions — year", () => { + it("marks the same year correct with no direction hint", () => { + const twin = { ...AHRI, releaseDate: 2013 }; + const result = cell(twin, AATROX, "releaseDate"); + expect(result.result).toBe("correct"); + expect(result.direction).toBeUndefined(); + }); + + it("points up when the guess is older than the target", () => { + expect(cell(AHRI, AATROX, "releaseDate")).toMatchObject({ + result: "wrong", + direction: "up", + }); + }); + + it("points down when the guess is newer than the target", () => { + expect(cell(BELVETH, AATROX, "releaseDate")).toMatchObject({ + result: "wrong", + direction: "down", + }); + }); + + it("gives no direction when either year is unparseable", () => { + const undated = { ...AHRI, releaseDate: "" }; + const result = cell(undated, AATROX, "releaseDate"); + expect(result.result).toBe("wrong"); + expect(result.direction).toBeUndefined(); + }); + + it("renders a missing year as ?", () => { + const undated = { ...AHRI, releaseDate: "" }; + expect(cell(undated, AATROX, "releaseDate").guessValue).toBe("?"); + }); + + it("passes the year through unformatted", () => { + expect(cell(AHRI, AATROX, "releaseDate")).toMatchObject({ + guessValue: 2011, + targetValue: 2013, + }); + }); +}); + +describe("compareChampions — value formatting", () => { + it("capitalizes gender", () => { + expect(cell(AATROX, AATROX, "gender").guessValue).toBe("Male"); + }); + + it("renders attackType close as Melee and range as Ranged", () => { + expect(cell(AATROX, AATROX, "attackType").guessValue).toBe("Melee"); + expect(cell(AHRI, AHRI, "attackType").guessValue).toBe("Ranged"); + }); + + it("splits hyphenated regions into capitalized words", () => { + expect(cell(AKALI, AKALI, "region").guessValue).toBe("Shadow Isles"); + }); + + it("title-cases comma lists and rejoins them with a space", () => { + expect(cell(AHRI, AHRI, "genre").guessValue).toBe("Mage, Assassin"); + expect(cell(AKALI, AKALI, "lane").guessValue).toBe("Top, Mid"); + }); + + it("renders an empty value as an em dash", () => { + expect(cell(BELVETH, BELVETH, "resource").guessValue).toBe("—"); + }); + + it("formats the target value the same way as the guess value", () => { + expect(cell(AHRI, AKALI, "region")).toMatchObject({ + guessValue: "Ionia", + targetValue: "Shadow Isles", + }); + }); +}); diff --git a/test/fixtures/champions.js b/test/fixtures/champions.js new file mode 100644 index 0000000..3e4b07f --- /dev/null +++ b/test/fixtures/champions.js @@ -0,0 +1,66 @@ +/** + * Hand-written champion fixtures, deliberately small so the weekly champions.json + * sync can never break the suite. Shapes mirror the real data exactly: + * `releaseDate` is a number, `attackType` is "close" | "range". + * + * Chosen to exercise every compareChampions branch against AATROX as target: + * AHRI — disjoint genre, disjoint lane, older year (direction "up") + * AKALI — disjoint genre, overlapping lane (partial), hyphenated region + * BELVETH — identical genre (correct), disjoint lane, empty resource ("—"), + * newer year (direction "down") + */ + +export const AATROX = { + id: "Aatrox", + name: "Aatrox", + title: "the Darkin Blade", + resource: "Blood Well", + genre: "Fighter", + gender: "male", + attackType: "close", + releaseDate: 2013, + region: "runeterra", + lane: "top", +}; + +export const AHRI = { + id: "Ahri", + name: "Ahri", + title: "the Nine-Tailed Fox", + resource: "Mana", + genre: "Mage,Assassin", + gender: "female", + attackType: "range", + releaseDate: 2011, + region: "ionia", + lane: "mid", +}; + +export const AKALI = { + id: "Akali", + name: "Akali", + title: "the Rogue Assassin", + resource: "Energy", + genre: "Assassin", + gender: "female", + attackType: "close", + releaseDate: 2010, + region: "shadow-isles", + lane: "top,mid", +}; + +export const BELVETH = { + id: "Belveth", + name: "Bel'Veth", + title: "the Empress of the Void", + resource: "", + genre: "Fighter", + gender: "female", + attackType: "close", + releaseDate: 2022, + region: "void", + lane: "jungle", +}; + +/** Load order matters for getRandomChampion index assertions. */ +export const CHAMPIONS = [AATROX, AHRI, AKALI, BELVETH]; diff --git a/test/game-engine.test.js b/test/game-engine.test.js new file mode 100644 index 0000000..0d658d2 --- /dev/null +++ b/test/game-engine.test.js @@ -0,0 +1,357 @@ +import { describe, expect, it } from "vitest"; +import { + clearExpiredCache, + clearUnlimitedState, + createGame, + getOrCreateUnlimitedSeed, + loadUnlimitedStats, + saveUnlimitedStats, + submitGuess, +} from "../lib/game-engine"; +import { AATROX, AHRI } from "./fixtures/champions"; + +/** Minimal stand-in for compareChampions — the engine only stores what this returns. */ +const compareFn = (guess, target) => [ + { key: "name", result: guess.name === target.name ? "correct" : "wrong" }, +]; + +/** Synthesize a distinct non-target champion. */ +function other(name) { + return { ...AHRI, id: name, name }; +} + +function daily(overrides = {}) { + return createGame({ + target: AATROX, + compareFn, + maxGuesses: 6, + mode: "daily", + seed: "2026-07-25", + ...overrides, + }); +} + +function unlimited(overrides = {}) { + return createGame({ + target: AATROX, + compareFn, + maxGuesses: 0, + mode: "unlimited", + seed: "unlimited_123", + ...overrides, + }); +} + +describe("createGame", () => { + it("starts empty and open", () => { + const game = daily(); + expect(game.guesses).toEqual([]); + expect(game.results).toEqual([]); + expect(game.isOver).toBe(false); + expect(game.isWon).toBe(false); + }); + + it("carries config through onto the game object", () => { + expect(daily()).toMatchObject({ + target: AATROX, + maxGuesses: 6, + mode: "daily", + seed: "2026-07-25", + }); + }); + + it("defaults to 6 guesses in daily mode when maxGuesses is omitted", () => { + const game = createGame({ target: AATROX, compareFn }); + expect(game.maxGuesses).toBe(6); + expect(game.mode).toBe("daily"); + }); +}); + +describe("submitGuess", () => { + it("appends the guess and its comparison result", () => { + const game = submitGuess(daily(), AHRI); + expect(game.guesses).toEqual([AHRI]); + expect(game.results).toHaveLength(1); + }); + + it("does not mutate the game it was given", () => { + const before = daily(); + submitGuess(before, AHRI); + expect(before.guesses).toHaveLength(0); + expect(before.results).toHaveLength(0); + }); + + it("returns fresh array identities so reassignment is observable", () => { + const before = daily(); + const after = submitGuess(before, AHRI); + expect(after).not.toBe(before); + expect(after.guesses).not.toBe(before.guesses); + expect(after.results).not.toBe(before.results); + }); + + it("rejects a duplicate guess with null", () => { + const game = submitGuess(daily(), AHRI); + expect(submitGuess(game, AHRI)).toBeNull(); + }); + + it("rejects any guess once the game is over", () => { + const game = submitGuess(daily(), AATROX); + expect(game.isOver).toBe(true); + expect(submitGuess(game, AHRI)).toBeNull(); + }); + + it("wins on the correct champion", () => { + expect(submitGuess(daily(), AATROX)).toMatchObject({ + isWon: true, + isOver: true, + }); + }); + + it("still counts a win made with the final allowed guess", () => { + let game = daily(); + for (const name of ["a", "b", "c", "d", "e"]) { + game = submitGuess(game, other(name)); + } + expect(game.isOver).toBe(false); + + game = submitGuess(game, AATROX); + expect(game).toMatchObject({ isWon: true, isOver: true }); + expect(game.guesses).toHaveLength(6); + }); + + it("ends the game as a loss at maxGuesses wrong guesses", () => { + let game = daily(); + for (const name of ["a", "b", "c", "d", "e", "f"]) { + game = submitGuess(game, other(name)); + } + expect(game).toMatchObject({ isOver: true, isWon: false }); + expect(game.guesses).toHaveLength(6); + }); + + it("never ends on guess count when maxGuesses is 0", () => { + let game = unlimited(); + for (let i = 0; i < 10; i++) { + game = submitGuess(game, other(`c${i}`)); + } + expect(game.isOver).toBe(false); + expect(game.guesses).toHaveLength(10); + }); +}); + +describe("persistence — storage key contract", () => { + it("writes daily state to exactly loldle_daily_", () => { + submitGuess(daily(), AHRI); + expect(localStorage.getItem("loldle_daily_2026-07-25")).not.toBeNull(); + }); + + it("writes unlimited state to exactly loldle_unlimited_current", () => { + submitGuess(unlimited(), AHRI); + expect(localStorage.getItem("loldle_unlimited_current")).not.toBeNull(); + }); + + it("ignores the seed when keying unlimited state", () => { + submitGuess(unlimited({ seed: "unlimited_999" }), AHRI); + expect(localStorage.getItem("loldle_unlimited_current")).not.toBeNull(); + expect(localStorage.getItem("loldle_unlimited_unlimited_999")).toBeNull(); + }); + + it("stores the target name, guesses, results and flags", () => { + submitGuess(daily(), AHRI); + const saved = JSON.parse(localStorage.getItem("loldle_daily_2026-07-25")); + expect(saved).toMatchObject({ + targetName: "Aatrox", + guesses: [AHRI], + isOver: false, + isWon: false, + }); + expect(saved.results).toHaveLength(1); + }); +}); + +describe("persistence — restore on createGame", () => { + it("restores an in-progress game for the same target", () => { + submitGuess(daily(), AHRI); + const restored = daily(); + expect(restored.guesses).toEqual([AHRI]); + expect(restored.results).toHaveLength(1); + }); + + it("restores the over and won flags", () => { + submitGuess(daily(), AATROX); + expect(daily()).toMatchObject({ isOver: true, isWon: true }); + }); + + it("discards saved state when the target changed — the date-rollover case", () => { + submitGuess(daily(), AHRI); + const fresh = createGame({ + target: { ...AHRI, name: "Ahri" }, + compareFn, + maxGuesses: 6, + mode: "daily", + seed: "2026-07-25", + }); + expect(fresh.guesses).toEqual([]); + expect(fresh.isOver).toBe(false); + }); + + it("starts fresh when storage holds malformed JSON", () => { + localStorage.setItem("loldle_daily_2026-07-25", "{not json"); + expect(daily().guesses).toEqual([]); + }); +}); + +describe("clearExpiredCache", () => { + it("removes yesterday's daily entry but keeps today's", () => { + localStorage.setItem("loldle_daily_2026-07-24", "{}"); + localStorage.setItem("loldle_daily_2026-07-25", "{}"); + + clearExpiredCache("2026-07-25"); + + expect(localStorage.getItem("loldle_daily_2026-07-24")).toBeNull(); + expect(localStorage.getItem("loldle_daily_2026-07-25")).not.toBeNull(); + }); + + it("leaves unlimited keys untouched", () => { + localStorage.setItem("loldle_unlimited_current", "{}"); + localStorage.setItem("loldle_unlimited_stats", "{}"); + localStorage.setItem("loldle_unlimited_seed", "s"); + + clearExpiredCache("2026-07-25"); + + expect(localStorage.getItem("loldle_unlimited_current")).not.toBeNull(); + expect(localStorage.getItem("loldle_unlimited_stats")).not.toBeNull(); + expect(localStorage.getItem("loldle_unlimited_seed")).not.toBeNull(); + }); + + it("removes every stale daily entry, not just the first", () => { + localStorage.setItem("loldle_daily_2026-07-20", "{}"); + localStorage.setItem("loldle_daily_2026-07-21", "{}"); + localStorage.setItem("loldle_daily_2026-07-22", "{}"); + + clearExpiredCache("2026-07-25"); + + expect(localStorage.length).toBe(0); + }); + + it("ignores unrelated keys", () => { + localStorage.setItem("unrelated", "keep"); + clearExpiredCache("2026-07-25"); + expect(localStorage.getItem("unrelated")).toBe("keep"); + }); +}); + +describe("unlimited seed", () => { + it("creates and persists a seed on first call", () => { + const seed = getOrCreateUnlimitedSeed(); + expect(seed).toMatch(/^unlimited_/); + expect(localStorage.getItem("loldle_unlimited_seed")).toBe(seed); + }); + + it("returns the same seed on subsequent calls", () => { + expect(getOrCreateUnlimitedSeed()).toBe(getOrCreateUnlimitedSeed()); + }); + + it("clearUnlimitedState drops both the seed and the current game", () => { + submitGuess(unlimited(), AHRI); + getOrCreateUnlimitedSeed(); + + clearUnlimitedState(); + + expect(localStorage.getItem("loldle_unlimited_current")).toBeNull(); + expect(localStorage.getItem("loldle_unlimited_seed")).toBeNull(); + }); + + it("clearUnlimitedState preserves accumulated stats", () => { + localStorage.setItem("loldle_unlimited_stats", '{"gamesPlayed":3}'); + clearUnlimitedState(); + expect(loadUnlimitedStats().gamesPlayed).toBe(3); + }); +}); + +describe("saveUnlimitedStats", () => { + /** + * Play an unlimited game to completion; `won` picks the ending. + * Clears saved state first, mirroring handleNewGame — otherwise createGame + * restores the previous finished game and submitGuess refuses to run. + */ + function finish(won, wrongGuesses = 0) { + clearUnlimitedState(); + let game = unlimited(); + for (let i = 0; i < wrongGuesses; i++) { + game = submitGuess(game, other(`c${i}`)); + } + if (won) game = submitGuess(game, AATROX); + else game = { ...game, isOver: true }; + return game; + } + + it("counts a played game", () => { + saveUnlimitedStats(finish(false)); + expect(loadUnlimitedStats().gamesPlayed).toBe(1); + }); + + it("counts a win and records its guess count", () => { + saveUnlimitedStats(finish(true, 2)); + expect(loadUnlimitedStats()).toMatchObject({ + gamesPlayed: 1, + gamesWon: 1, + guessDistribution: { 3: 1 }, + }); + }); + + it("does not count a win when the game was lost", () => { + saveUnlimitedStats(finish(false)); + expect(loadUnlimitedStats()).toMatchObject({ + gamesPlayed: 1, + gamesWon: 0, + guessDistribution: {}, + }); + }); + + it("accumulates across games", () => { + saveUnlimitedStats(finish(true, 1)); + saveUnlimitedStats(finish(true, 1)); + saveUnlimitedStats(finish(false)); + + expect(loadUnlimitedStats()).toMatchObject({ + gamesPlayed: 3, + gamesWon: 2, + guessDistribution: { 2: 2 }, + }); + }); + + it("stamps lastPlayed", () => { + saveUnlimitedStats(finish(true, 0)); + expect(typeof loadUnlimitedStats().lastPlayed).toBe("number"); + }); + + it("is a no-op for daily mode", () => { + const game = submitGuess(daily(), AATROX); + saveUnlimitedStats(game); + expect(localStorage.getItem("loldle_unlimited_stats")).toBeNull(); + }); + + it("is a no-op for an unfinished game", () => { + saveUnlimitedStats(submitGuess(unlimited(), AHRI)); + expect(localStorage.getItem("loldle_unlimited_stats")).toBeNull(); + }); +}); + +describe("loadUnlimitedStats", () => { + it("returns zeroed stats when nothing is stored", () => { + expect(loadUnlimitedStats()).toEqual({ + gamesPlayed: 0, + gamesWon: 0, + guessDistribution: {}, + lastPlayed: null, + }); + }); + + it("returns zeroed stats when storage holds malformed JSON", () => { + localStorage.setItem("loldle_unlimited_stats", "not json at all"); + expect(loadUnlimitedStats()).toMatchObject({ + gamesPlayed: 0, + gamesWon: 0, + }); + }); +}); diff --git a/test/setup-local-storage.js b/test/setup-local-storage.js new file mode 100644 index 0000000..0bf8ec5 --- /dev/null +++ b/test/setup-local-storage.js @@ -0,0 +1,37 @@ +import { beforeEach, vi } from "vitest"; + +/** + * Minimal spec-shaped localStorage stub. + * `key(i)` and `length` are required: clearExpiredCache iterates by index. + */ +class LocalStorageStub { + #store = new Map(); + + get length() { + return this.#store.size; + } + + key(i) { + return [...this.#store.keys()][i] ?? null; + } + + getItem(k) { + return this.#store.has(k) ? this.#store.get(k) : null; + } + + setItem(k, v) { + this.#store.set(k, String(v)); + } + + removeItem(k) { + this.#store.delete(k); + } + + clear() { + this.#store.clear(); + } +} + +beforeEach(() => { + vi.stubGlobal("localStorage", new LocalStorageStub()); +}); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..b4c77f4 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + setupFiles: ["./test/setup-local-storage.js"], + include: ["test/**/*.test.js"], + }, +}); From 2990fcc152d676014e754ed396426927ef70aaee Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 25 Jul 2026 11:00:14 +0700 Subject: [PATCH 2/5] build: scaffold SvelteKit alongside the Next.js app Add svelte.config.js (adapter-static, BASE_PATH-driven base), vite.config.js (Tailwind v4 Vite plugin ahead of sveltekit), the src/ shell, and static/ holding a copy of champions.json plus .nojekyll. Declares "type": "module" so Vite can load the .js config files; Next.js was only unaffected because all its configs use .mjs. jsconfig keeps the @/* alias so pnpm next:build stays runnable for side-by-side comparison during the port. Next.js scripts move to next:* and both toolchains build from this commit. --- .gitignore | 3 + jsconfig.json | 6 + package.json | 17 +- pnpm-lock.yaml | 260 ++- src/app.css | 122 ++ src/app.html | 11 + src/routes/+layout.js | 1 + src/routes/+layout.svelte | 12 + src/routes/+page.svelte | 9 + static/.nojekyll | 0 static/champions.json | 3803 +++++++++++++++++++++++++++++++++++++ svelte.config.js | 15 + vite.config.js | 8 + 13 files changed, 4260 insertions(+), 7 deletions(-) create mode 100644 src/app.css create mode 100644 src/app.html create mode 100644 src/routes/+layout.js create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+page.svelte create mode 100644 static/.nojekyll create mode 100644 static/champions.json create mode 100644 svelte.config.js create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore index fda67f1..4b6375c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ chrome-dev-tools /.next/ /out/ +# sveltekit +/.svelte-kit/ + # production /build diff --git a/jsconfig.json b/jsconfig.json index 2a2e4b3..4be8af7 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,6 +1,12 @@ { + "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { + "allowJs": true, + "checkJs": false, + "module": "esnext", + "moduleResolution": "bundler", "paths": { + "$app/types": ["./.svelte-kit/types/index.d.ts"], "@/*": ["./*"] } } diff --git a/package.json b/package.json index bc2b25f..60ef9d4 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,17 @@ "name": "loldle", "version": "0.1.0", "private": true, + "type": "module", "packageManager": "pnpm@11.1.1", "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", "lint": "eslint", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "next:dev": "next dev", + "next:build": "next build" }, "dependencies": { "next": "16.2.10", @@ -17,10 +20,16 @@ "react-dom": "19.2.7" }, "devDependencies": { + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.70.1", + "@sveltejs/vite-plugin-svelte": "^7.2.0", "@tailwindcss/postcss": "^4.3.3", + "@tailwindcss/vite": "^4.3.3", "eslint": "^9", "eslint-config-next": "16.2.10", + "svelte": "^5.56.7", "tailwindcss": "^4.3.3", + "vite": "^8.1.5", "vitest": "^4.1.10" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65bb00b..c9e840e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,18 +23,36 @@ importers: specifier: 19.2.7 version: 19.2.7(react@19.2.7) devDependencies: + '@sveltejs/adapter-static': + specifier: ^3.0.10 + version: 3.0.10(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0))) + '@sveltejs/kit': + specifier: ^2.70.1 + version: 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0)) + '@sveltejs/vite-plugin-svelte': + specifier: ^7.2.0 + version: 7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)) '@tailwindcss/postcss': specifier: ^4.3.3 version: 4.3.3 + '@tailwindcss/vite': + specifier: ^4.3.3 + version: 4.3.3(vite@8.1.5(jiti@2.7.0)) eslint: specifier: ^9 version: 9.39.5(jiti@2.7.0) eslint-config-next: specifier: 16.2.10 version: 16.2.10(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + svelte: + specifier: ^5.56.7 + version: 5.56.7(@typescript-eslint/types@8.59.4) tailwindcss: specifier: ^4.3.3 version: 4.3.3 + vite: + specifier: ^8.1.5 + version: 8.1.5(jiti@2.7.0) vitest: specifier: ^4.1.10 version: 4.1.10(vite@8.1.5(jiti@2.7.0)) @@ -463,6 +481,9 @@ packages: '@oxc-project/types@0.139.0': resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rolldown/binding-android-arm64@1.1.5': resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -567,6 +588,39 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@sveltejs/acorn-typescript@1.0.11': + resolution: {integrity: sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-static@3.0.10': + resolution: {integrity: sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.70.1': + resolution: {integrity: sha512-nY9SPHGOZro3doud9vZXDBwl9tCZIouuJztjgSHs6PAIrv9M/z5O7eOhPV5xU7CgVHA976Jwu3BA1hIFvXztkA==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.0.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ^5.3.3 || ^6.0.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + typescript: + optional: true + + '@sveltejs/vite-plugin-svelte@7.2.0': + resolution: {integrity: sha512-1SpkuMSRLfugrVX+IrKfE1RUegzo8AQzKQ6qQPfVzbcWi5IhuTPaKb5ZrLpucleFznkc4/RTeSPoRnGWFxX+EQ==} + engines: {node: ^20.19 || ^22.12 || >=24} + peerDependencies: + svelte: ^5.46.4 + vite: ^8.0.0-beta.7 || ^8.0.0 + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -662,6 +716,11 @@ packages: '@tailwindcss/postcss@4.3.3': resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} @@ -671,6 +730,9 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -683,6 +745,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@typescript-eslint/eslint-plugin@8.59.4': resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -911,6 +976,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -1028,6 +1097,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1041,6 +1114,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1080,6 +1157,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1092,6 +1173,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devalue@5.8.2: + resolution: {integrity: sha512-DObPPAfdtFbXjxLqK8s2Xk9ZuWz5+ZoFEhC7J76es4GU/rEiXwHTmbImoCdyoCOcBH1UF3+Cz6Z2sYD4hyl5TA==} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -1253,6 +1337,9 @@ packages: jiti: optional: true + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1261,6 +1348,14 @@ packages: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} + esrap@2.3.0: + resolution: {integrity: sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==} + peerDependencies: + '@typescript-eslint/types': ^8.2.0 + peerDependenciesMeta: + '@typescript-eslint/types': + optional: true + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -1513,6 +1608,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1600,6 +1698,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -1685,6 +1787,9 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1724,6 +1829,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1944,6 +2053,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@3.1.2: + resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -1987,6 +2099,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2056,6 +2172,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte@5.56.7: + resolution: {integrity: sha512-5qERUZX80oQj6XrDMUmD2Uhd/cIpCPDWWKBK3ZHmyRUC9apPyamWM8xMo31mbWsIQxwG2hVoSnOJ/EcnhVkkzQ==} + engines: {node: '>=18'} + tailwindcss@4.3.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} @@ -2086,6 +2206,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -2189,6 +2313,14 @@ packages: yaml: optional: true + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + vitest@4.1.10: resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2267,6 +2399,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} @@ -2663,6 +2798,8 @@ snapshots: '@oxc-project/types@0.139.0': {} + '@polka/url@1.0.0-next.29': {} + '@rolldown/binding-android-arm64@1.1.5': optional: true @@ -2718,6 +2855,43 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@sveltejs/acorn-typescript@1.0.11(acorn@8.16.0)': + dependencies: + acorn: 8.16.0 + + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0)))': + dependencies: + '@sveltejs/kit': 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0)) + + '@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0))': + dependencies: + '@standard-schema/spec': 1.1.0 + '@sveltejs/acorn-typescript': 1.0.11(acorn@8.16.0) + '@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)) + '@types/cookie': 0.6.0 + acorn: 8.16.0 + cookie: 0.6.0 + devalue: 5.8.2 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + set-cookie-parser: 3.1.2 + sirv: 3.0.2 + svelte: 5.56.7(@typescript-eslint/types@8.59.4) + vite: 8.1.5(jiti@2.7.0) + optionalDependencies: + typescript: 6.0.3 + + '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0))': + dependencies: + deepmerge: 4.3.1 + magic-string: 0.30.21 + obug: 2.1.4 + svelte: 5.56.7(@typescript-eslint/types@8.59.4) + vite: 8.1.5(jiti@2.7.0) + vitefu: 1.1.3(vite@8.1.5(jiti@2.7.0)) + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -2791,6 +2965,13 @@ snapshots: postcss: 8.5.19 tailwindcss: 4.3.3 + '@tailwindcss/vite@4.3.3(vite@8.1.5(jiti@2.7.0))': + dependencies: + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: 8.1.5(jiti@2.7.0) + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 @@ -2806,6 +2987,8 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/cookie@0.6.0': {} + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.9': {} @@ -2814,6 +2997,8 @@ snapshots: '@types/json5@0.0.29': {} + '@types/trusted-types@2.0.7': {} + '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -2883,7 +3068,7 @@ snapshots: debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -3035,6 +3220,8 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.1: {} + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -3175,6 +3362,8 @@ snapshots: client-only@0.0.1: {} + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3185,6 +3374,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@0.6.0: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3221,6 +3412,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -3235,6 +3428,8 @@ snapshots: detect-libc@2.1.2: {} + devalue@5.8.2: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -3548,6 +3743,8 @@ snapshots: transitivePeerDependencies: - supports-color + esm-env@1.2.2: {} + espree@10.4.0: dependencies: acorn: 8.16.0 @@ -3558,6 +3755,12 @@ snapshots: dependencies: estraverse: 5.3.0 + esrap@2.3.0(@typescript-eslint/types@8.59.4): + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + optionalDependencies: + '@typescript-eslint/types': 8.59.4 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -3594,6 +3797,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3805,6 +4012,10 @@ snapshots: is-number@7.0.0: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.9 + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -3890,6 +4101,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kleur@4.1.5: {} + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -3950,6 +4163,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + locate-character@3.0.0: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3987,6 +4202,8 @@ snapshots: minimist@1.2.8: {} + mrmime@2.0.1: {} + ms@2.1.3: {} nanoid@3.3.12: {} @@ -4229,6 +4446,8 @@ snapshots: semver@7.8.1: {} + set-cookie-parser@3.1.2: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -4319,6 +4538,12 @@ snapshots: siginfo@2.0.0: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + source-map-js@1.2.1: {} stable-hash@0.0.5: {} @@ -4399,6 +4624,27 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte@5.56.7(@typescript-eslint/types@8.59.4): + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.11(acorn@8.16.0) + '@types/estree': 1.0.9 + '@types/trusted-types': 2.0.7 + acorn: 8.16.0 + aria-query: 5.3.1 + axobject-query: 4.1.0 + clsx: 2.1.1 + devalue: 5.8.2 + esm-env: 1.2.2 + esrap: 2.3.0(@typescript-eslint/types@8.59.4) + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.21 + zimmerframe: 1.1.4 + transitivePeerDependencies: + - '@typescript-eslint/types' + tailwindcss@4.3.3: {} tapable@2.3.3: {} @@ -4414,8 +4660,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 tinyrainbow@3.1.0: {} @@ -4423,6 +4669,8 @@ snapshots: dependencies: is-number: 7.0.0 + totalist@3.0.1: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -4541,6 +4789,10 @@ snapshots: fsevents: 2.3.3 jiti: 2.7.0 + vitefu@1.1.3(vite@8.1.5(jiti@2.7.0)): + optionalDependencies: + vite: 8.1.5(jiti@2.7.0) + vitest@4.1.10(vite@8.1.5(jiti@2.7.0)): dependencies: '@vitest/expect': 4.1.10 @@ -4622,6 +4874,8 @@ snapshots: yocto-queue@0.1.0: {} + zimmerframe@1.1.4: {} + zod-validation-error@4.0.2(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..b4d482e --- /dev/null +++ b/src/app.css @@ -0,0 +1,122 @@ +@import "tailwindcss"; + +:root { + --color-bg: #1a1a2e; + --color-surface: #16213e; + --color-surface-hover: #1a2745; + --color-text: #e0e0e0; + --color-text-muted: #8a8a9a; + --color-correct: #4ecb71; + --color-partial: #f4b35e; + --color-wrong: #d85b5b; + --color-header: #2a2a4a; + --color-input-bg: #0f3460; + --color-input-border: #533483; + --color-accent: #533483; + --cell-size: 90px; +} + +body { + background: var(--color-bg); + color: var(--color-text); + font-family: "Segoe UI", system-ui, -apple-system, sans-serif; + min-height: 100vh; +} + +/* Guess grid layout */ +.guess-row { + display: grid; + grid-template-columns: 130px repeat(7, var(--cell-size)); + gap: 4px; + margin-bottom: 4px; + min-width: fit-content; +} + +.guess-cell { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: 8px 4px; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 500; + min-height: 56px; + word-break: break-word; + animation: cellReveal 0.3s ease forwards; + opacity: 0; +} + +.header-row { + margin-bottom: 8px; +} + +.header-cell { + background: var(--color-header); + color: var(--color-text-muted); + font-weight: 600; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 1; + animation: none; +} + +.champion-cell { + display: flex; + align-items: center; + gap: 8px; + justify-content: flex-start; + padding-left: 8px; + background: var(--color-surface); +} + +.cell-correct { + background: var(--color-correct); + color: #1a1a2e; +} + +.cell-partial { + background: var(--color-partial); + color: #1a1a2e; +} + +.cell-wrong { + background: var(--color-wrong); + color: #fff; +} + +@keyframes cellReveal { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* Responsive */ +@media (max-width: 768px) { + :root { + --cell-size: 72px; + } + .guess-row { + grid-template-columns: 100px repeat(7, var(--cell-size)); + } + .guess-cell { + font-size: 0.7rem; + min-height: 48px; + padding: 6px 2px; + } +} + +@media (max-width: 480px) { + :root { + --cell-size: 60px; + } + .guess-row { + grid-template-columns: 80px repeat(7, var(--cell-size)); + } +} diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..adf8bd8 --- /dev/null +++ b/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/routes/+layout.js b/src/routes/+layout.js new file mode 100644 index 0000000..189f71e --- /dev/null +++ b/src/routes/+layout.js @@ -0,0 +1 @@ +export const prerender = true; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..a8dbb88 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,12 @@ + + + + LoLdle + + + +{@render children()} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..bbf213e --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,9 @@ + +
+
+

+ LoLdle +

+
+
diff --git a/static/.nojekyll b/static/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/static/champions.json b/static/champions.json new file mode 100644 index 0000000..9c8a61d --- /dev/null +++ b/static/champions.json @@ -0,0 +1,3803 @@ +[ + { + "id": "Aatrox", + "name": "Aatrox", + "title": "the Darkin Blade", + "resource": "Blood Well", + "genre": "Fighter", + "skinCount": 41, + "image": { + "full": "Aatrox.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "runeterra", + "lane": "top" + }, + { + "id": "Ahri", + "name": "Ahri", + "title": "the Nine-Tailed Fox", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 95, + "image": { + "full": "Ahri.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "ionia", + "lane": "mid" + }, + { + "id": "Akali", + "name": "Akali", + "title": "the Rogue Assassin", + "resource": "Energy", + "genre": "Assassin", + "skinCount": 101, + "image": { + "full": "Akali.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Akshan", + "name": "Akshan", + "title": "the Rogue Sentinel", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 30, + "image": { + "full": "Akshan.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2021, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Alistar", + "name": "Alistar", + "title": "the Minotaur", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 46, + "image": { + "full": "Alistar.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Ambessa", + "name": "Ambessa", + "title": "Matriarch of War", + "resource": "Energy", + "genre": "Fighter,Assassin", + "skinCount": 15, + "image": { + "full": "Ambessa.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2024, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Amumu", + "name": "Amumu", + "title": "the Sad Mummy", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 62, + "image": { + "full": "Amumu.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "Anivia", + "name": "Anivia", + "title": "the Cryophoenix", + "resource": "Mana", + "genre": "Mage", + "skinCount": 55, + "image": { + "full": "Anivia.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "freljord", + "lane": "mid" + }, + { + "id": "Annie", + "name": "Annie", + "title": "the Dark Child", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 59, + "image": { + "full": "Annie.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "mid" + }, + { + "id": "Aphelios", + "name": "Aphelios", + "title": "the Weapon of the Faithful", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 40, + "image": { + "full": "Aphelios.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2019, + "region": "mount-targon", + "lane": "bottom" + }, + { + "id": "Ashe", + "name": "Ashe", + "title": "the Frost Archer", + "resource": "Mana", + "genre": "Marksman,Support", + "skinCount": 93, + "image": { + "full": "Ashe.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "freljord", + "lane": "bottom,support" + }, + { + "id": "AurelionSol", + "name": "Aurelion Sol", + "title": "The Star Forger", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "AurelionSol.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2016, + "region": "mount-targon", + "lane": "mid" + }, + { + "id": "Aurora", + "name": "Aurora", + "title": "the Witch Between Worlds", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 29, + "image": { + "full": "Aurora.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2024, + "region": "freljord", + "lane": "top,mid" + }, + { + "id": "Azir", + "name": "Azir", + "title": "the Emperor of the Sands", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 28, + "image": { + "full": "Azir.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2014, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Bard", + "name": "Bard", + "title": "the Wandering Caretaker", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 46, + "image": { + "full": "Bard.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2015, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Belveth", + "name": "Bel'Veth", + "title": "the Empress of the Void", + "resource": "", + "genre": "Fighter", + "skinCount": 29, + "image": { + "full": "Belveth.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2022, + "region": "void", + "lane": "jungle" + }, + { + "id": "Blitzcrank", + "name": "Blitzcrank", + "title": "the Great Steam Golem", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 74, + "image": { + "full": "Blitzcrank.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "support" + }, + { + "id": "Brand", + "name": "Brand", + "title": "the Burning Vengeance", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 54, + "image": { + "full": "Brand.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "runeterra", + "lane": "jungle,support" + }, + { + "id": "Braum", + "name": "Braum", + "title": "the Heart of the Freljord", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 61, + "image": { + "full": "Braum.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2014, + "region": "freljord", + "lane": "support" + }, + { + "id": "Briar", + "name": "Briar", + "title": "the Restrained Hunger", + "resource": "Fury", + "genre": "Fighter,Assassin", + "skinCount": 29, + "image": { + "full": "Briar.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2023, + "region": "noxus", + "lane": "jungle" + }, + { + "id": "Caitlyn", + "name": "Caitlyn", + "title": "the Sheriff of Piltover", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 63, + "image": { + "full": "Caitlyn.png", + "sprite": "champion0.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "piltover", + "lane": "bottom" + }, + { + "id": "Camille", + "name": "Camille", + "title": "the Steel Shadow", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 53, + "image": { + "full": "Camille.png", + "sprite": "champion0.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2016, + "region": "piltover", + "lane": "top" + }, + { + "id": "Cassiopeia", + "name": "Cassiopeia", + "title": "the Serpent's Embrace", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "Cassiopeia.png", + "sprite": "champion0.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Chogath", + "name": "Cho'Gath", + "title": "the Terror of the Void", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 42, + "image": { + "full": "Chogath.png", + "sprite": "champion0.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "divers", + "attackType": "close", + "releaseDate": 2009, + "region": "void", + "lane": "top" + }, + { + "id": "Corki", + "name": "Corki", + "title": "the Daring Bombardier", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 45, + "image": { + "full": "Corki.png", + "sprite": "champion0.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "mid" + }, + { + "id": "Darius", + "name": "Darius", + "title": "the Hand of Noxus", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 73, + "image": { + "full": "Darius.png", + "sprite": "champion0.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "noxus", + "lane": "top" + }, + { + "id": "Diana", + "name": "Diana", + "title": "Scorn of the Moon", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 83, + "image": { + "full": "Diana.png", + "sprite": "champion0.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "mount-targon", + "lane": "jungle,mid" + }, + { + "id": "DrMundo", + "name": "Dr. Mundo", + "title": "the Madman of Zaun", + "resource": "None", + "genre": "Tank,Fighter", + "skinCount": 30, + "image": { + "full": "DrMundo.png", + "sprite": "champion0.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "top" + }, + { + "id": "Draven", + "name": "Draven", + "title": "the Glorious Executioner", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 78, + "image": { + "full": "Draven.png", + "sprite": "champion0.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "noxus", + "lane": "bottom" + }, + { + "id": "Ekko", + "name": "Ekko", + "title": "the Boy Who Shattered Time", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 59, + "image": { + "full": "Ekko.png", + "sprite": "champion0.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2015, + "region": "zaun", + "lane": "jungle,mid" + }, + { + "id": "Elise", + "name": "Elise", + "title": "the Spider Queen", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 36, + "image": { + "full": "Elise.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Evelynn", + "name": "Evelynn", + "title": "Agony's Embrace", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 65, + "image": { + "full": "Evelynn.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Ezreal", + "name": "Ezreal", + "title": "the Prodigal Explorer", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 76, + "image": { + "full": "Ezreal.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "piltover", + "lane": "bottom" + }, + { + "id": "Fiddlesticks", + "name": "Fiddlesticks", + "title": "the Ancient Fear", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 55, + "image": { + "full": "Fiddlesticks.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Fiora", + "name": "Fiora", + "title": "the Grand Duelist", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 100, + "image": { + "full": "Fiora.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "demacia", + "lane": "top" + }, + { + "id": "Fizz", + "name": "Fizz", + "title": "the Tidal Trickster", + "resource": "Mana", + "genre": "Assassin,Fighter", + "skinCount": 44, + "image": { + "full": "Fizz.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bilgewater", + "lane": "mid" + }, + { + "id": "Galio", + "name": "Galio", + "title": "the Colossus", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 54, + "image": { + "full": "Galio.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "mid" + }, + { + "id": "Gangplank", + "name": "Gangplank", + "title": "the Saltwater Scourge", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 42, + "image": { + "full": "Gangplank.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Garen", + "name": "Garen", + "title": "The Might of Demacia", + "resource": "None", + "genre": "Fighter,Tank", + "skinCount": 54, + "image": { + "full": "Garen.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "top" + }, + { + "id": "Gnar", + "name": "Gnar", + "title": "the Missing Link", + "resource": "Rage", + "genre": "Fighter,Tank", + "skinCount": 44, + "image": { + "full": "Gnar.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2014, + "region": "freljord", + "lane": "top" + }, + { + "id": "Gragas", + "name": "Gragas", + "title": "the Rabble Rouser", + "resource": "Mana", + "genre": "Fighter,Mage", + "skinCount": 48, + "image": { + "full": "Gragas.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Graves", + "name": "Graves", + "title": "the Outlaw", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 54, + "image": { + "full": "Graves.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bilgewater", + "lane": "jungle" + }, + { + "id": "Gwen", + "name": "Gwen", + "title": "The Hallowed Seamstress", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 48, + "image": { + "full": "Gwen.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "top" + }, + { + "id": "Hecarim", + "name": "Hecarim", + "title": "the Shadow of War", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 50, + "image": { + "full": "Hecarim.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Heimerdinger", + "name": "Heimerdinger", + "title": "the Revered Inventor", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 27, + "image": { + "full": "Heimerdinger.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "piltover", + "lane": "support" + }, + { + "id": "Hwei", + "name": "Hwei", + "title": "the Visionary", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 22, + "image": { + "full": "Hwei.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2023, + "region": "ionia", + "lane": "mid,support" + }, + { + "id": "Illaoi", + "name": "Illaoi", + "title": "the Kraken Priestess", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 46, + "image": { + "full": "Illaoi.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2015, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Irelia", + "name": "Irelia", + "title": "the Blade Dancer", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 72, + "image": { + "full": "Irelia.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Ivern", + "name": "Ivern", + "title": "the Green Father", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 39, + "image": { + "full": "Ivern.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2016, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Janna", + "name": "Janna", + "title": "the Storm's Fury", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 67, + "image": { + "full": "Janna.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "zaun", + "lane": "support" + }, + { + "id": "JarvanIV", + "name": "Jarvan IV", + "title": "the Exemplar of Demacia", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 55, + "image": { + "full": "JarvanIV.png", + "sprite": "champion1.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Jax", + "name": "Jax", + "title": "Grandmaster at Arms", + "resource": "Mana", + "genre": "Fighter", + "skinCount": 42, + "image": { + "full": "Jax.png", + "sprite": "champion1.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "top,jungle" + }, + { + "id": "Jayce", + "name": "Jayce", + "title": "the Defender of Tomorrow", + "resource": "Mana", + "genre": "Fighter,Marksman", + "skinCount": 47, + "image": { + "full": "Jayce.png", + "sprite": "champion1.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "piltover", + "lane": "top" + }, + { + "id": "Jhin", + "name": "Jhin", + "title": "the Virtuoso", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 63, + "image": { + "full": "Jhin.png", + "sprite": "champion1.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2016, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Jinx", + "name": "Jinx", + "title": "the Loose Cannon", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 64, + "image": { + "full": "Jinx.png", + "sprite": "champion1.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "KSante", + "name": "K'Sante", + "title": "the Pride of Nazumah", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 18, + "image": { + "full": "KSante.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2022, + "region": "shurima", + "lane": "top" + }, + { + "id": "Kaisa", + "name": "Kai'Sa", + "title": "Daughter of the Void", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 79, + "image": { + "full": "Kaisa.png", + "sprite": "champion1.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2018, + "region": "void", + "lane": "bottom" + }, + { + "id": "Kalista", + "name": "Kalista", + "title": "the Spear of Vengeance", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 33, + "image": { + "full": "Kalista.png", + "sprite": "champion1.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2014, + "region": "shadow-isles", + "lane": "bottom" + }, + { + "id": "Karma", + "name": "Karma", + "title": "the Enlightened One", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 76, + "image": { + "full": "Karma.png", + "sprite": "champion1.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "ionia", + "lane": "mid,support" + }, + { + "id": "Karthus", + "name": "Karthus", + "title": "the Deathsinger", + "resource": "Mana", + "genre": "Mage", + "skinCount": 44, + "image": { + "full": "Karthus.png", + "sprite": "champion1.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Kassadin", + "name": "Kassadin", + "title": "the Void Walker", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 32, + "image": { + "full": "Kassadin.png", + "sprite": "champion1.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "void", + "lane": "mid" + }, + { + "id": "Katarina", + "name": "Katarina", + "title": "the Sinister Blade", + "resource": "None", + "genre": "Assassin,Mage", + "skinCount": 78, + "image": { + "full": "Katarina.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Kayle", + "name": "Kayle", + "title": "the Righteous", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 88, + "image": { + "full": "Kayle.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "demacia", + "lane": "top" + }, + { + "id": "Kayn", + "name": "Kayn", + "title": "the Shadow Reaper", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 31, + "image": { + "full": "Kayn.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Kennen", + "name": "Kennen", + "title": "the Heart of the Tempest", + "resource": "Energy", + "genre": "Mage", + "skinCount": 34, + "image": { + "full": "Kennen.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "ionia", + "lane": "top" + }, + { + "id": "Khazix", + "name": "Kha'Zix", + "title": "the Voidreaver", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 58, + "image": { + "full": "Khazix.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "void", + "lane": "jungle" + }, + { + "id": "Kindred", + "name": "Kindred", + "title": "The Eternal Hunters", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 44, + "image": { + "full": "Kindred.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2015, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Kled", + "name": "Kled", + "title": "the Cantankerous Cavalier", + "resource": "Courage", + "genre": "Fighter", + "skinCount": 27, + "image": { + "full": "Kled.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2016, + "region": "noxus", + "lane": "top" + }, + { + "id": "KogMaw", + "name": "Kog'Maw", + "title": "the Mouth of the Abyss", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 65, + "image": { + "full": "KogMaw.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "void", + "lane": "bottom" + }, + { + "id": "Leblanc", + "name": "LeBlanc", + "title": "the Deceiver", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 61, + "image": { + "full": "Leblanc.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "LeeSin", + "name": "Lee Sin", + "title": "the Blind Monk", + "resource": "Energy", + "genre": "Fighter,Assassin", + "skinCount": 80, + "image": { + "full": "LeeSin.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Leona", + "name": "Leona", + "title": "the Radiant Dawn", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 71, + "image": { + "full": "Leona.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Lillia", + "name": "Lillia", + "title": "the Bashful Bloom", + "resource": "Mana", + "genre": "Fighter,Mage", + "skinCount": 53, + "image": { + "full": "Lillia.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Lissandra", + "name": "Lissandra", + "title": "the Ice Witch", + "resource": "Mana", + "genre": "Mage", + "skinCount": 52, + "image": { + "full": "Lissandra.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "freljord", + "lane": "mid" + }, + { + "id": "Locke", + "name": "Locke", + "title": "the Ashen Exorcist", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 10, + "image": { + "full": "Locke.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "region": "demacia" + }, + { + "id": "Lucian", + "name": "Lucian", + "title": "the Purifier", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 73, + "image": { + "full": "Lucian.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2013, + "region": "demacia", + "lane": "bottom" + }, + { + "id": "Lulu", + "name": "Lulu", + "title": "the Fae Sorceress", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 56, + "image": { + "full": "Lulu.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "bandle-city", + "lane": "support" + }, + { + "id": "Lux", + "name": "Lux", + "title": "the Lady of Luminosity", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 81, + "image": { + "full": "Lux.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "demacia", + "lane": "mid,support" + }, + { + "id": "Malphite", + "name": "Malphite", + "title": "Shard of the Monolith", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 57, + "image": { + "full": "Malphite.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "ixtal", + "lane": "top" + }, + { + "id": "Malzahar", + "name": "Malzahar", + "title": "the Prophet of the Void", + "resource": "Mana", + "genre": "Mage", + "skinCount": 58, + "image": { + "full": "Malzahar.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "void", + "lane": "mid" + }, + { + "id": "Maokai", + "name": "Maokai", + "title": "the Twisted Treant", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 36, + "image": { + "full": "Maokai.png", + "sprite": "champion2.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shadow-isles", + "lane": "support" + }, + { + "id": "MasterYi", + "name": "Master Yi", + "title": "the Wuju Bladesman", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 90, + "image": { + "full": "MasterYi.png", + "sprite": "champion2.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Mel", + "name": "Mel", + "title": "the Soul's Reflection", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 12, + "image": { + "full": "Mel.png", + "sprite": "champion2.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2025, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Milio", + "name": "Milio", + "title": "The Gentle Flame", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 29, + "image": { + "full": "Milio.png", + "sprite": "champion2.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2023, + "region": "ixtal", + "lane": "support" + }, + { + "id": "MissFortune", + "name": "Miss Fortune", + "title": "the Bounty Hunter", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 76, + "image": { + "full": "MissFortune.png", + "sprite": "champion2.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "bilgewater", + "lane": "bottom" + }, + { + "id": "Mordekaiser", + "name": "Mordekaiser", + "title": "the Iron Revenant", + "resource": "Shield", + "genre": "Fighter,Mage", + "skinCount": 57, + "image": { + "full": "Mordekaiser.png", + "sprite": "champion2.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "noxus", + "lane": "top" + }, + { + "id": "Morgana", + "name": "Morgana", + "title": "the Fallen", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 82, + "image": { + "full": "Morgana.png", + "sprite": "champion2.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2009, + "region": "demacia", + "lane": "support" + }, + { + "id": "Naafiri", + "name": "Naafiri", + "title": "the Hound of a Hundred Bites", + "resource": "Mana", + "genre": "Assassin,Fighter", + "skinCount": 38, + "image": { + "full": "Naafiri.png", + "sprite": "champion2.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2023, + "region": "shurima", + "lane": "mid" + }, + { + "id": "Nami", + "name": "Nami", + "title": "the Tidecaller", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 68, + "image": { + "full": "Nami.png", + "sprite": "champion2.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Nasus", + "name": "Nasus", + "title": "the Curator of the Sands", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 63, + "image": { + "full": "Nasus.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "top" + }, + { + "id": "Nautilus", + "name": "Nautilus", + "title": "the Titan of the Depths", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 46, + "image": { + "full": "Nautilus.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "bilgewater", + "lane": "support" + }, + { + "id": "Neeko", + "name": "Neeko", + "title": "the Curious Chameleon", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 50, + "image": { + "full": "Neeko.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2018, + "region": "ixtal", + "lane": "mid,support" + }, + { + "id": "Nidalee", + "name": "Nidalee", + "title": "the Bestial Huntress", + "resource": "Mana", + "genre": "Assassin,Mage", + "skinCount": 67, + "image": { + "full": "Nidalee.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "ixtal", + "lane": "jungle" + }, + { + "id": "Nilah", + "name": "Nilah", + "title": "the Joy Unbound", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 30, + "image": { + "full": "Nilah.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2022, + "region": "runeterra", + "lane": "bottom" + }, + { + "id": "Nocturne", + "name": "Nocturne", + "title": "the Eternal Nightmare", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 36, + "image": { + "full": "Nocturne.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "divers", + "attackType": "close", + "releaseDate": 2011, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Nunu", + "name": "Nunu & Willump", + "title": "the Boy and His Yeti", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 54, + "image": { + "full": "Nunu.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Olaf", + "name": "Olaf", + "title": "the Berserker", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 53, + "image": { + "full": "Olaf.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "top" + }, + { + "id": "Orianna", + "name": "Orianna", + "title": "the Lady of Clockwork", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 40, + "image": { + "full": "Orianna.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "piltover", + "lane": "mid" + }, + { + "id": "Ornn", + "name": "Ornn", + "title": "The Fire below the Mountain", + "resource": "Mana", + "genre": "Tank", + "skinCount": 38, + "image": { + "full": "Ornn.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "freljord", + "lane": "top" + }, + { + "id": "Pantheon", + "name": "Pantheon", + "title": "the Unbreakable Spear", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 47, + "image": { + "full": "Pantheon.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "mount-targon", + "lane": "top,support" + }, + { + "id": "Poppy", + "name": "Poppy", + "title": "Keeper of the Hammer", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 51, + "image": { + "full": "Poppy.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "top,jungle" + }, + { + "id": "Pyke", + "name": "Pyke", + "title": "the Bloodharbor Ripper", + "resource": "Mana", + "genre": "Support,Assassin", + "skinCount": 85, + "image": { + "full": "Pyke.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2018, + "region": "bilgewater", + "lane": "support" + }, + { + "id": "Qiyana", + "name": "Qiyana", + "title": "Empress of the Elements", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 51, + "image": { + "full": "Qiyana.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2019, + "region": "ixtal", + "lane": "mid" + }, + { + "id": "Quinn", + "name": "Quinn", + "title": "Demacia's Wings", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 33, + "image": { + "full": "Quinn.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2013, + "region": "demacia", + "lane": "top" + }, + { + "id": "Rakan", + "name": "Rakan", + "title": "The Charmer", + "resource": "Mana", + "genre": "Support", + "skinCount": 58, + "image": { + "full": "Rakan.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2017, + "region": "ionia", + "lane": "support" + }, + { + "id": "Rammus", + "name": "Rammus", + "title": "the Armordillo", + "resource": "Mana", + "genre": "Tank", + "skinCount": 35, + "image": { + "full": "Rammus.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "RekSai", + "name": "Rek'Sai", + "title": "the Void Burrower", + "resource": "Rage", + "genre": "Fighter,Tank", + "skinCount": 36, + "image": { + "full": "RekSai.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2014, + "region": "void", + "lane": "jungle" + }, + { + "id": "Rell", + "name": "Rell", + "title": "the Iron Maiden", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 31, + "image": { + "full": "Rell.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2020, + "region": "noxus", + "lane": "support" + }, + { + "id": "Renata", + "name": "Renata Glasc", + "title": "the Chem-Baroness", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 32, + "image": { + "full": "Renata.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2022, + "region": "zaun", + "lane": "support" + }, + { + "id": "Renekton", + "name": "Renekton", + "title": "the Butcher of the Sands", + "resource": "Fury", + "genre": "Fighter,Tank", + "skinCount": 57, + "image": { + "full": "Renekton.png", + "sprite": "champion3.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shurima", + "lane": "top" + }, + { + "id": "Rengar", + "name": "Rengar", + "title": "the Pridestalker", + "resource": "Ferocity", + "genre": "Assassin,Fighter", + "skinCount": 59, + "image": { + "full": "Rengar.png", + "sprite": "champion3.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "ixtal", + "lane": "jungle" + }, + { + "id": "Riven", + "name": "Riven", + "title": "the Exile", + "resource": "None", + "genre": "Fighter,Assassin", + "skinCount": 73, + "image": { + "full": "Riven.png", + "sprite": "champion3.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "noxus", + "lane": "top" + }, + { + "id": "Rumble", + "name": "Rumble", + "title": "the Mechanized Menace", + "resource": "Heat", + "genre": "Fighter,Mage", + "skinCount": 32, + "image": { + "full": "Rumble.png", + "sprite": "champion3.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "bandle-city", + "lane": "top" + }, + { + "id": "Ryze", + "name": "Ryze", + "title": "the Rune Mage", + "resource": "Mana", + "genre": "Mage", + "skinCount": 39, + "image": { + "full": "Ryze.png", + "sprite": "champion3.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "mid" + }, + { + "id": "Samira", + "name": "Samira", + "title": "the Desert Rose", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 42, + "image": { + "full": "Samira.png", + "sprite": "champion3.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2020, + "region": "noxus", + "lane": "bottom" + }, + { + "id": "Sejuani", + "name": "Sejuani", + "title": "Fury of the North", + "resource": "Mana", + "genre": "Tank", + "skinCount": 57, + "image": { + "full": "Sejuani.png", + "sprite": "champion3.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "freljord", + "lane": "jungle" + }, + { + "id": "Senna", + "name": "Senna", + "title": "the Redeemer", + "resource": "Mana", + "genre": "Support,Marksman", + "skinCount": 80, + "image": { + "full": "Senna.png", + "sprite": "champion3.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2019, + "region": "runeterra", + "lane": "bottom,support" + }, + { + "id": "Seraphine", + "name": "Seraphine", + "title": "the Starry-Eyed Songstress", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 75, + "image": { + "full": "Seraphine.png", + "sprite": "champion3.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2020, + "region": "piltover", + "lane": "bottom,support" + }, + { + "id": "Sett", + "name": "Sett", + "title": "the Boss", + "resource": "Grit", + "genre": "Fighter,Tank", + "skinCount": 76, + "image": { + "full": "Sett.png", + "sprite": "champion3.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "top" + }, + { + "id": "Shaco", + "name": "Shaco", + "title": "the Demon Jester", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 73, + "image": { + "full": "Shaco.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "runeterra", + "lane": "jungle" + }, + { + "id": "Shen", + "name": "Shen", + "title": "the Eye of Twilight", + "resource": "Energy", + "genre": "Tank", + "skinCount": 62, + "image": { + "full": "Shen.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "ionia", + "lane": "top" + }, + { + "id": "Shyvana", + "name": "Shyvana", + "title": "the Half-Dragon", + "resource": "Fury", + "genre": "Fighter,Tank", + "skinCount": 26, + "image": { + "full": "Shyvana.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2011, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Singed", + "name": "Singed", + "title": "the Mad Chemist", + "resource": "Mana", + "genre": "Tank,Mage", + "skinCount": 29, + "image": { + "full": "Singed.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "top" + }, + { + "id": "Sion", + "name": "Sion", + "title": "The Undead Juggernaut", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 59, + "image": { + "full": "Sion.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "noxus", + "lane": "top" + }, + { + "id": "Sivir", + "name": "Sivir", + "title": "the Battle Mistress", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 81, + "image": { + "full": "Sivir.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "shurima", + "lane": "bottom" + }, + { + "id": "Skarner", + "name": "Skarner", + "title": "the Primordial Sovereign", + "resource": "Mana", + "genre": "Tank,Fighter", + "skinCount": 15, + "image": { + "full": "Skarner.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ixtal", + "lane": "top,jungle" + }, + { + "id": "Smolder", + "name": "Smolder", + "title": "the Fiery Fledgling", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 20, + "image": { + "full": "Smolder.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2024, + "region": "runeterra", + "lane": "bottom" + }, + { + "id": "Sona", + "name": "Sona", + "title": "Maven of the Strings", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 77, + "image": { + "full": "Sona.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2010, + "region": "demacia", + "lane": "support" + }, + { + "id": "Soraka", + "name": "Soraka", + "title": "the Starchild", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 63, + "image": { + "full": "Soraka.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Swain", + "name": "Swain", + "title": "the Noxian Grand General", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 51, + "image": { + "full": "Swain.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2010, + "region": "noxus", + "lane": "support" + }, + { + "id": "Sylas", + "name": "Sylas", + "title": "the Unshackled", + "resource": "Mana", + "genre": "Mage,Assassin", + "skinCount": 54, + "image": { + "full": "Sylas.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2019, + "region": "demacia", + "lane": "mid" + }, + { + "id": "Syndra", + "name": "Syndra", + "title": "the Dark Sovereign", + "resource": "Mana", + "genre": "Mage", + "skinCount": 74, + "image": { + "full": "Syndra.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "ionia", + "lane": "mid" + }, + { + "id": "TahmKench", + "name": "Tahm Kench", + "title": "The River King", + "resource": "Mana", + "genre": "Tank,Support", + "skinCount": 47, + "image": { + "full": "TahmKench.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2015, + "region": "bilgewater", + "lane": "top" + }, + { + "id": "Taliyah", + "name": "Taliyah", + "title": "the Stoneweaver", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 25, + "image": { + "full": "Taliyah.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2016, + "region": "shurima", + "lane": "jungle" + }, + { + "id": "Talon", + "name": "Talon", + "title": "the Blade's Shadow", + "resource": "Mana", + "genre": "Assassin", + "skinCount": 60, + "image": { + "full": "Talon.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Taric", + "name": "Taric", + "title": "the Shield of Valoran", + "resource": "Mana", + "genre": "Support,Tank", + "skinCount": 36, + "image": { + "full": "Taric.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "mount-targon", + "lane": "support" + }, + { + "id": "Teemo", + "name": "Teemo", + "title": "the Swift Scout", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 63, + "image": { + "full": "Teemo.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "top" + }, + { + "id": "Thresh", + "name": "Thresh", + "title": "the Chain Warden", + "resource": "Mana", + "genre": "Support,Tank", + "skinCount": 68, + "image": { + "full": "Thresh.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "shadow-isles", + "lane": "support" + }, + { + "id": "Tristana", + "name": "Tristana", + "title": "the Yordle Gunner", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 79, + "image": { + "full": "Tristana.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "bottom" + }, + { + "id": "Trundle", + "name": "Trundle", + "title": "the Troll King", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 30, + "image": { + "full": "Trundle.png", + "sprite": "champion4.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "freljord", + "lane": "top" + }, + { + "id": "Tryndamere", + "name": "Tryndamere", + "title": "the Barbarian King", + "resource": "Fury", + "genre": "Fighter,Assassin", + "skinCount": 47, + "image": { + "full": "Tryndamere.png", + "sprite": "champion4.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "top" + }, + { + "id": "TwistedFate", + "name": "Twisted Fate", + "title": "the Card Master", + "resource": "Mana", + "genre": "Mage,Marksman", + "skinCount": 54, + "image": { + "full": "TwistedFate.png", + "sprite": "champion4.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bilgewater", + "lane": "mid,bottom" + }, + { + "id": "Twitch", + "name": "Twitch", + "title": "the Plague Rat", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 59, + "image": { + "full": "Twitch.png", + "sprite": "champion4.png", + "group": "champion", + "x": 144, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Udyr", + "name": "Udyr", + "title": "the Spirit Walker", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 12, + "image": { + "full": "Udyr.png", + "sprite": "champion4.png", + "group": "champion", + "x": 192, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "freljord", + "lane": "top,jungle" + }, + { + "id": "Urgot", + "name": "Urgot", + "title": "the Dreadnought", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 41, + "image": { + "full": "Urgot.png", + "sprite": "champion4.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "zaun", + "lane": "top" + }, + { + "id": "Varus", + "name": "Varus", + "title": "the Arrow of Retribution", + "resource": "Mana", + "genre": "Marksman,Mage", + "skinCount": 71, + "image": { + "full": "Varus.png", + "sprite": "champion4.png", + "group": "champion", + "x": 288, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Vayne", + "name": "Vayne", + "title": "the Night Hunter", + "resource": "Mana", + "genre": "Marksman,Assassin", + "skinCount": 74, + "image": { + "full": "Vayne.png", + "sprite": "champion4.png", + "group": "champion", + "x": 336, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2011, + "region": "demacia", + "lane": "bottom" + }, + { + "id": "Veigar", + "name": "Veigar", + "title": "the Tiny Master of Evil", + "resource": "Mana", + "genre": "Mage", + "skinCount": 67, + "image": { + "full": "Veigar.png", + "sprite": "champion4.png", + "group": "champion", + "x": 384, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "bandle-city", + "lane": "mid" + }, + { + "id": "Velkoz", + "name": "Vel'Koz", + "title": "the Eye of the Void", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 36, + "image": { + "full": "Velkoz.png", + "sprite": "champion4.png", + "group": "champion", + "x": 432, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2014, + "region": "void", + "lane": "support" + }, + { + "id": "Vex", + "name": "Vex", + "title": "the Gloomist", + "resource": "Mana", + "genre": "Mage", + "skinCount": 38, + "image": { + "full": "Vex.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "mid" + }, + { + "id": "Vi", + "name": "Vi", + "title": "the Piltover Enforcer", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 51, + "image": { + "full": "Vi.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2012, + "region": "piltover", + "lane": "jungle" + }, + { + "id": "Viego", + "name": "Viego", + "title": "The Ruined King", + "resource": "None", + "genre": "Fighter,Assassin", + "skinCount": 43, + "image": { + "full": "Viego.png", + "sprite": "champion5.png", + "group": "champion", + "x": 96, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2021, + "region": "shadow-isles", + "lane": "jungle" + }, + { + "id": "Viktor", + "name": "Viktor", + "title": "the Herald of the Arcane", + "resource": "Mana", + "genre": "Mage", + "skinCount": 34, + "image": { + "full": "Viktor.png", + "sprite": "champion5.png", + "group": "champion", + "x": 144, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "zaun", + "lane": "mid" + }, + { + "id": "Vladimir", + "name": "Vladimir", + "title": "the Crimson Reaper", + "resource": "Crimson Rush", + "genre": "Mage,Fighter", + "skinCount": 58, + "image": { + "full": "Vladimir.png", + "sprite": "champion5.png", + "group": "champion", + "x": 192, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "noxus", + "lane": "mid" + }, + { + "id": "Volibear", + "name": "Volibear", + "title": "the Relentless Storm", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 37, + "image": { + "full": "Volibear.png", + "sprite": "champion5.png", + "group": "champion", + "x": 240, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "freljord", + "lane": "top" + }, + { + "id": "Warwick", + "name": "Warwick", + "title": "the Uncaged Wrath of Zaun", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 61, + "image": { + "full": "Warwick.png", + "sprite": "champion5.png", + "group": "champion", + "x": 288, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2009, + "region": "zaun", + "lane": "jungle" + }, + { + "id": "MonkeyKing", + "name": "Wukong", + "title": "the Monkey King", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 36, + "image": { + "full": "MonkeyKing.png", + "sprite": "champion2.png", + "group": "champion", + "x": 240, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "ionia", + "lane": "jungle" + }, + { + "id": "Xayah", + "name": "Xayah", + "title": "the Rebel", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 65, + "image": { + "full": "Xayah.png", + "sprite": "champion5.png", + "group": "champion", + "x": 336, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2017, + "region": "ionia", + "lane": "bottom" + }, + { + "id": "Xerath", + "name": "Xerath", + "title": "the Magus Ascendant", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 33, + "image": { + "full": "Xerath.png", + "sprite": "champion5.png", + "group": "champion", + "x": 384, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2011, + "region": "shurima", + "lane": "mid,support" + }, + { + "id": "XinZhao", + "name": "Xin Zhao", + "title": "the Seneschal of Demacia", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 59, + "image": { + "full": "XinZhao.png", + "sprite": "champion5.png", + "group": "champion", + "x": 432, + "y": 0, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2010, + "region": "demacia", + "lane": "jungle" + }, + { + "id": "Yasuo", + "name": "Yasuo", + "title": "the Unforgiven", + "resource": "Flow", + "genre": "Fighter,Assassin", + "skinCount": 97, + "image": { + "full": "Yasuo.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "ionia", + "lane": "top,mid,bottom" + }, + { + "id": "Yone", + "name": "Yone", + "title": "the Unforgotten", + "resource": "Flow", + "genre": "Fighter,Assassin", + "skinCount": 76, + "image": { + "full": "Yone.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2020, + "region": "ionia", + "lane": "top,mid" + }, + { + "id": "Yorick", + "name": "Yorick", + "title": "Shepherd of Souls", + "resource": "Mana", + "genre": "Fighter,Tank", + "skinCount": 59, + "image": { + "full": "Yorick.png", + "sprite": "champion5.png", + "group": "champion", + "x": 96, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2011, + "region": "shadow-isles", + "lane": "top" + }, + { + "id": "Yunara", + "name": "Yunara", + "title": "the Unbroken Faith", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 17, + "image": { + "full": "Yunara.png", + "sprite": "champion5.png", + "group": "champion", + "x": 144, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2026, + "region": "ionia" + }, + { + "id": "Yuumi", + "name": "Yuumi", + "title": "the Magical Cat", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 79, + "image": { + "full": "Yuumi.png", + "sprite": "champion5.png", + "group": "champion", + "x": 192, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "close", + "releaseDate": 2019, + "region": "bandle-city", + "lane": "support" + }, + { + "id": "Zaahen", + "name": "Zaahen", + "title": "The Unsundered", + "resource": "Mana", + "genre": "Fighter,Assassin", + "skinCount": 10, + "image": { + "full": "Zaahen.png", + "sprite": "champion5.png", + "group": "champion", + "x": 240, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "region": "runeterra" + }, + { + "id": "Zac", + "name": "Zac", + "title": "the Secret Weapon", + "resource": "None", + "genre": "Tank,Fighter", + "skinCount": 42, + "image": { + "full": "Zac.png", + "sprite": "champion5.png", + "group": "champion", + "x": 288, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2013, + "region": "zaun", + "lane": "jungle" + }, + { + "id": "Zed", + "name": "Zed", + "title": "the Master of Shadows", + "resource": "Energy", + "genre": "Assassin", + "skinCount": 71, + "image": { + "full": "Zed.png", + "sprite": "champion5.png", + "group": "champion", + "x": 336, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "close", + "releaseDate": 2012, + "region": "ionia", + "lane": "mid" + }, + { + "id": "Zeri", + "name": "Zeri", + "title": "The Spark of Zaun", + "resource": "Mana", + "genre": "Marksman", + "skinCount": 47, + "image": { + "full": "Zeri.png", + "sprite": "champion5.png", + "group": "champion", + "x": 384, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2022, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Ziggs", + "name": "Ziggs", + "title": "the Hexplosives Expert", + "resource": "Mana", + "genre": "Mage", + "skinCount": 52, + "image": { + "full": "Ziggs.png", + "sprite": "champion5.png", + "group": "champion", + "x": 432, + "y": 48, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2012, + "region": "zaun", + "lane": "bottom" + }, + { + "id": "Zilean", + "name": "Zilean", + "title": "the Chronokeeper", + "resource": "Mana", + "genre": "Support,Mage", + "skinCount": 33, + "image": { + "full": "Zilean.png", + "sprite": "champion5.png", + "group": "champion", + "x": 0, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "male", + "attackType": "range", + "releaseDate": 2009, + "region": "runeterra", + "lane": "support" + }, + { + "id": "Zoe", + "name": "Zoe", + "title": "the Aspect of Twilight", + "resource": "Mana", + "genre": "Mage", + "skinCount": 45, + "image": { + "full": "Zoe.png", + "sprite": "champion5.png", + "group": "champion", + "x": 48, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2017, + "region": "mount-targon", + "lane": "mid" + }, + { + "id": "Zyra", + "name": "Zyra", + "title": "Rise of the Thorns", + "resource": "Mana", + "genre": "Mage,Support", + "skinCount": 74, + "image": { + "full": "Zyra.png", + "sprite": "champion5.png", + "group": "champion", + "x": 96, + "y": 96, + "w": 48, + "h": 48 + }, + "gender": "female", + "attackType": "range", + "releaseDate": 2012, + "region": "ixtal", + "lane": "support" + } +] diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..78ac63d --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,15 @@ +import adapter from "@sveltejs/adapter-static"; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + // No options: output goes to build/. No fallback either — every route is + // prerendered, so a fallback page would mask a real prerender failure. + adapter: adapter(), + paths: { + base: process.env.BASE_PATH || "", + }, + }, +}; + +export default config; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..ebfff1b --- /dev/null +++ b/vite.config.js @@ -0,0 +1,8 @@ +import { sveltekit } from "@sveltejs/kit/vite"; +import tailwindcss from "@tailwindcss/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + // tailwindcss() must precede sveltekit(), per the Tailwind v4 SvelteKit guide. + plugins: [tailwindcss(), sveltekit()], +}); From f253dfb4b0a4534310e8d80ffb3c2a70cd531fe3 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 25 Jul 2026 11:01:40 +0700 Subject: [PATCH 3/5] refactor: port lib layer to src/lib classic-mode.js moves byte-for-byte. champion-data.js swaps the Next.js env-var fetch prefix for asset() from $app/paths, which resolves to /champions.json in dev and /loldle/champions.json in the deployed build. getChampionImageUrl keeps returning an absolute Data Dragon URL, unwrapped. Drops three exports with no callers: getAllChampions, getChampionByName, getGuessedNames. The characterization suite passes unchanged against the new location -- only import specifiers moved, no assertions touched. --- src/lib/champion-data.js | 75 +++++++++++++++ src/lib/classic-mode.js | 109 +++++++++++++++++++++ src/lib/game-engine.js | 191 +++++++++++++++++++++++++++++++++++++ test/champion-data.test.js | 9 +- test/classic-mode.test.js | 2 +- test/game-engine.test.js | 2 +- vitest.config.js | 7 ++ 7 files changed, 391 insertions(+), 4 deletions(-) create mode 100644 src/lib/champion-data.js create mode 100644 src/lib/classic-mode.js create mode 100644 src/lib/game-engine.js diff --git a/src/lib/champion-data.js b/src/lib/champion-data.js new file mode 100644 index 0000000..1c32e44 --- /dev/null +++ b/src/lib/champion-data.js @@ -0,0 +1,75 @@ +// Data layer: load, search, and select champions + +import { asset } from "$app/paths"; + +let champions = []; +let loadPromise = null; + +/** Fetch and parse champions data (deduplicated) */ +export async function loadChampions() { + if (champions.length > 0) return champions; + if (loadPromise) return loadPromise; + + loadPromise = (async () => { + const response = await fetch(asset("/champions.json")); + if (!response.ok) { + loadPromise = null; + throw new Error(`Failed to load champions: ${response.status}`); + } + champions = await response.json(); + return champions; + })(); + + return loadPromise; +} + +/** Filter champions for autocomplete (prefix-first, then substring) */ +export function searchChampions(query, excludeNames = []) { + if (!query) return []; + const lower = query.toLowerCase(); + const excluded = new Set(excludeNames.map((n) => n.toLowerCase())); + + return champions + .filter( + (c) => + c.name.toLowerCase().includes(lower) && + !excluded.has(c.name.toLowerCase()), + ) + .sort((a, b) => { + const aStarts = a.name.toLowerCase().startsWith(lower) ? 0 : 1; + const bStarts = b.name.toLowerCase().startsWith(lower) ? 0 : 1; + return aStarts - bStarts || a.name.localeCompare(b.name); + }) + .slice(0, 8); +} + +/** Seeded random champion selection (deterministic for same seed string) */ +export function getRandomChampion(seed) { + if (!champions.length) return null; + const hash = hashString(seed); + const index = hash % champions.length; + return champions[index]; +} + +/** Simple string hash (djb2) */ +function hashString(str) { + let hash = 5381; + for (let i = 0; i < str.length; i++) { + hash = (hash * 33) ^ str.charCodeAt(i); + } + return hash >>> 0; +} + +/** Get today's local date string for daily seed */ +export function getTodaySeed() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, "0"); + const day = String(now.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +/** Get champion image URL from Data Dragon CDN */ +export function getChampionImageUrl(championId) { + return `https://ddragon.leagueoflegends.com/cdn/img/champion/tiles/${championId}_0.jpg`; +} diff --git a/src/lib/classic-mode.js b/src/lib/classic-mode.js new file mode 100644 index 0000000..2522ee2 --- /dev/null +++ b/src/lib/classic-mode.js @@ -0,0 +1,109 @@ +// Classic mode: compare two champions across 7 attributes + +/** Attributes displayed in order */ +export const CLASSIC_ATTRIBUTES = [ + { key: "gender", label: "Gender", type: "exact" }, + { key: "genre", label: "Genre", type: "multi" }, + { key: "attackType", label: "Range", type: "exact" }, + { key: "resource", label: "Resource", type: "exact" }, + { key: "region", label: "Region", type: "exact" }, + { key: "lane", label: "Lane", type: "multi" }, + { key: "releaseDate", label: "Year", type: "year" }, +]; + +/** Compare guess champion against target champion */ +export function compareChampions(guess, target) { + return CLASSIC_ATTRIBUTES.map((attr) => { + const guessVal = guess[attr.key] || ""; + const targetVal = target[attr.key] || ""; + + switch (attr.type) { + case "exact": + return { + ...attr, + guessValue: formatValue(attr.key, guessVal), + targetValue: formatValue(attr.key, targetVal), + result: guessVal.toLowerCase() === targetVal.toLowerCase() ? "correct" : "wrong", + }; + + case "multi": + return { + ...attr, + guessValue: formatValue(attr.key, guessVal), + targetValue: formatValue(attr.key, targetVal), + result: compareMultiValue(guessVal, targetVal), + }; + + case "year": + return { + ...attr, + guessValue: guessVal || "?", + targetValue: targetVal || "?", + ...compareYear(guessVal, targetVal), + }; + + default: + return { ...attr, guessValue: guessVal, targetValue: targetVal, result: "wrong" }; + } + }); +} + +function compareMultiValue(guessStr, targetStr) { + const guessSet = parseSet(guessStr); + const targetSet = parseSet(targetStr); + + if (guessSet.size === 0 && targetSet.size === 0) return "correct"; + if (guessSet.size === 0 || targetSet.size === 0) return "wrong"; + if (setsEqual(guessSet, targetSet)) return "correct"; + + for (const val of guessSet) { + if (targetSet.has(val)) return "partial"; + } + return "wrong"; +} + +function compareYear(guessYear, targetYear) { + const g = Number(guessYear); + const t = Number(targetYear); + + if (!g || !t) return { result: "wrong" }; + if (g === t) return { result: "correct" }; + return { result: "wrong", direction: g < t ? "up" : "down" }; +} + +function parseSet(str) { + if (!str) return new Set(); + return new Set( + str.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean), + ); +} + +function setsEqual(a, b) { + if (a.size !== b.size) return false; + for (const val of a) { + if (!b.has(val)) return false; + } + return true; +} + +function formatValue(key, value) { + if (!value) return "—"; + + switch (key) { + case "gender": + return capitalize(value); + case "attackType": + return value === "close" ? "Melee" : "Ranged"; + case "region": + return value.split("-").map(capitalize).join(" "); + case "genre": + case "lane": + return value.split(",").map((s) => capitalize(s.trim())).join(", "); + default: + return value; + } +} + +function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} diff --git a/src/lib/game-engine.js b/src/lib/game-engine.js new file mode 100644 index 0000000..7a5a4ed --- /dev/null +++ b/src/lib/game-engine.js @@ -0,0 +1,191 @@ +// Mode-agnostic game state machine with localStorage persistence + +const STORAGE_KEY_PREFIX = "loldle_"; +const UNLIMITED_SEED_KEY = `${STORAGE_KEY_PREFIX}unlimited_seed`; + +/** + * Create a new game instance + * @param {Object} config + * @param {Object} config.target - Target champion to guess + * @param {Function} config.compareFn - (guess, target) => comparison results + * @param {number} config.maxGuesses - Max allowed guesses (0 = unlimited) + * @param {string} config.mode - "daily" or "unlimited" + * @param {string} config.seed - Seed string for mode + */ +export function createGame(config) { + const { target, compareFn, maxGuesses = 6, mode = "daily", seed = "" } = config; + + // Try to restore saved state + const saved = loadState(mode, seed); + if (saved && saved.targetName === target.name) { + return { + target, + compareFn, + maxGuesses, + mode, + seed, + guesses: saved.guesses, + results: saved.results, + isOver: saved.isOver, + isWon: saved.isWon, + }; + } + + return { + target, + compareFn, + maxGuesses, + mode, + seed, + guesses: [], + results: [], + isOver: false, + isWon: false, + }; +} + +/** Submit a guess and return updated game state (immutable) */ +export function submitGuess(game, champion) { + if (game.isOver) return null; + if (game.guesses.some((g) => g.name === champion.name)) return null; + + const result = game.compareFn(champion, game.target); + const guesses = [...game.guesses, champion]; + const results = [...game.results, result]; + + let isWon = false; + let isOver = false; + + if (champion.name === game.target.name) { + isWon = true; + isOver = true; + } else if (game.maxGuesses > 0 && guesses.length >= game.maxGuesses) { + isOver = true; + } + + const updated = { ...game, guesses, results, isWon, isOver }; + saveState(updated); + return updated; +} + +/** Get or create a persistent seed for unlimited mode */ +export function getOrCreateUnlimitedSeed() { + try { + const saved = localStorage.getItem(UNLIMITED_SEED_KEY); + if (saved) return saved; + } catch { + // Ignore + } + return createNewUnlimitedSeed(); +} + +/** Create and persist a new unlimited seed */ +function createNewUnlimitedSeed() { + const seed = `unlimited_${Date.now()}_${Math.random()}`; + try { + localStorage.setItem(UNLIMITED_SEED_KEY, seed); + } catch { + // Ignore + } + return seed; +} + +/** Clear unlimited mode saved state (for new game) */ +export function clearUnlimitedState() { + try { + localStorage.removeItem(`${STORAGE_KEY_PREFIX}unlimited_current`); + localStorage.removeItem(UNLIMITED_SEED_KEY); + } catch { + // Ignore + } +} + +/** Save unlimited mode stats */ +export function saveUnlimitedStats(game) { + if (game.mode !== "unlimited" || !game.isOver) return; + + const key = `${STORAGE_KEY_PREFIX}unlimited_stats`; + let stats; + try { + stats = JSON.parse(localStorage.getItem(key)) || createEmptyStats(); + } catch { + stats = createEmptyStats(); + } + + stats.gamesPlayed++; + if (game.isWon) { + stats.gamesWon++; + const guessCount = game.guesses.length; + stats.guessDistribution[guessCount] = (stats.guessDistribution[guessCount] || 0) + 1; + } + stats.lastPlayed = Date.now(); + + try { + localStorage.setItem(key, JSON.stringify(stats)); + } catch { + // Ignore + } +} + +/** Load unlimited stats */ +export function loadUnlimitedStats() { + try { + return JSON.parse(localStorage.getItem(`${STORAGE_KEY_PREFIX}unlimited_stats`)) || createEmptyStats(); + } catch { + return createEmptyStats(); + } +} + +function createEmptyStats() { + return { gamesPlayed: 0, gamesWon: 0, guessDistribution: {}, lastPlayed: null }; +} + +function saveState(game) { + const key = getStorageKey(game.mode, game.seed); + const data = { + targetName: game.target.name, + guesses: game.guesses, + results: game.results, + isOver: game.isOver, + isWon: game.isWon, + timestamp: Date.now(), + }; + try { + localStorage.setItem(key, JSON.stringify(data)); + } catch { + // Ignore + } +} + +function loadState(mode, seed) { + const key = getStorageKey(mode, seed); + try { + const raw = localStorage.getItem(key); + if (!raw) return null; + return JSON.parse(raw); + } catch { + return null; + } +} + +/** Remove stale daily entries from localStorage (keeps only today's) */ +export function clearExpiredCache(todaySeed) { + try { + const todayKey = `${STORAGE_KEY_PREFIX}daily_${todaySeed}`; + const keysToRemove = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith(`${STORAGE_KEY_PREFIX}daily_`) && key !== todayKey) { + keysToRemove.push(key); + } + } + keysToRemove.forEach((key) => localStorage.removeItem(key)); + } catch { + // Ignore + } +} + +function getStorageKey(mode, seed) { + if (mode === "daily") return `${STORAGE_KEY_PREFIX}daily_${seed}`; + return `${STORAGE_KEY_PREFIX}unlimited_current`; +} diff --git a/test/champion-data.test.js b/test/champion-data.test.js index b4d01f6..d477766 100644 --- a/test/champion-data.test.js +++ b/test/champion-data.test.js @@ -1,7 +1,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { getChampionImageUrl, getTodaySeed } from "../lib/champion-data"; +import { getChampionImageUrl, getTodaySeed } from "$lib/champion-data"; import { AATROX, CHAMPIONS } from "./fixtures/champions"; +// $app/paths is a SvelteKit build-time construct with no meaning to a bare +// Vitest run. Base-path behaviour is verified by the production preview check, +// not here, so the identity stub is the honest unit-test substitute. +vi.mock("$app/paths", () => ({ asset: (p) => p })); + /** * `champions` is module-private with no reset export, so each group gets a fresh * module instance instead of adding production code for testability. @@ -14,7 +19,7 @@ async function freshModule({ champions = CHAMPIONS, ok = true, status = 200 } = json: async () => champions, })); vi.stubGlobal("fetch", fetchMock); - const mod = await import("../lib/champion-data"); + const mod = await import("$lib/champion-data"); return { mod, fetchMock }; } diff --git a/test/classic-mode.test.js b/test/classic-mode.test.js index b3df281..05ddaba 100644 --- a/test/classic-mode.test.js +++ b/test/classic-mode.test.js @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { CLASSIC_ATTRIBUTES, compareChampions } from "../lib/classic-mode"; +import { CLASSIC_ATTRIBUTES, compareChampions } from "$lib/classic-mode"; import { AATROX, AHRI, AKALI, BELVETH } from "./fixtures/champions"; /** Pull one attribute's comparison out of the 7-entry result array. */ diff --git a/test/game-engine.test.js b/test/game-engine.test.js index 0d658d2..10d2d50 100644 --- a/test/game-engine.test.js +++ b/test/game-engine.test.js @@ -7,7 +7,7 @@ import { loadUnlimitedStats, saveUnlimitedStats, submitGuess, -} from "../lib/game-engine"; +} from "$lib/game-engine"; import { AATROX, AHRI } from "./fixtures/champions"; /** Minimal stand-in for compareChampions — the engine only stores what this returns. */ diff --git a/vitest.config.js b/vitest.config.js index b4c77f4..ae15df0 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -1,6 +1,13 @@ +import { fileURLToPath } from "node:url"; import { defineConfig } from "vitest/config"; export default defineConfig({ + resolve: { + // Vitest does not read svelte.config.js, so $lib must be declared here too. + alias: { + $lib: fileURLToPath(new URL("./src/lib", import.meta.url)), + }, + }, test: { environment: "node", setupFiles: ["./test/setup-local-storage.js"], From bb4e8ea9b4231ae630a206ad4a8f791119e04b42 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 25 Jul 2026 11:14:35 +0700 Subject: [PATCH 4/5] feat: rewrite components in Svelte 5 runes Port all six React components. State becomes $state, memos become $derived, and the gameRef mirror that existed only to dodge a stale closure is gone -- runes read current values at call time, so one $state holds the game. Initialization moves to onMount rather than $effect: the page is prerendered, so loadChampions and localStorage must be browser-only, and onMount encodes that structurally instead of relying on the effect body happening to read no reactive state. champion-search becomes a real combobox/listbox: input carries the combobox role with aria-expanded and aria-activedescendant, options are list items wrapping buttons, and Escape closes the dropdown. Visual output is unchanged. next/image becomes plain img -- every usage already passed unoptimized. --- src/lib/components/champion-search.svelte | 122 ++++++++++++++++++ src/lib/components/game-board.svelte | 147 ++++++++++++++++++++++ src/lib/components/game-over.svelte | 46 +++++++ src/lib/components/guess-grid.svelte | 28 +++++ src/lib/components/guess-row.svelte | 35 ++++++ src/lib/components/stats-display.svelte | 29 +++++ src/routes/+page.svelte | 7 +- 7 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 src/lib/components/champion-search.svelte create mode 100644 src/lib/components/game-board.svelte create mode 100644 src/lib/components/game-over.svelte create mode 100644 src/lib/components/guess-grid.svelte create mode 100644 src/lib/components/guess-row.svelte create mode 100644 src/lib/components/stats-display.svelte diff --git a/src/lib/components/champion-search.svelte b/src/lib/components/champion-search.svelte new file mode 100644 index 0000000..59a69ba --- /dev/null +++ b/src/lib/components/champion-search.svelte @@ -0,0 +1,122 @@ + + +
+ = 0 + ? `champion-option-${activeIndex}` + : undefined} + bind:value={query} + oninput={() => { + activeIndex = -1; + isOpen = true; + }} + onkeydown={handleKeyDown} + {disabled} + placeholder={disabled ? "Game over" : "Type a champion name..."} + autocomplete="off" + class="w-full px-4 py-3 rounded-lg bg-[var(--color-input-bg)] border-2 border-[var(--color-input-border)] text-[var(--color-text)] text-base outline-none transition-colors focus:border-[var(--color-accent)] disabled:opacity-50 disabled:cursor-not-allowed placeholder:text-[var(--color-text-muted)]" + /> + + {#if isVisible} +
    + {#each matches as champion, i (champion.id)} +
  • + +
  • + {/each} +
+ {/if} +
diff --git a/src/lib/components/game-board.svelte b/src/lib/components/game-board.svelte new file mode 100644 index 0000000..c5989cd --- /dev/null +++ b/src/lib/components/game-board.svelte @@ -0,0 +1,147 @@ + + +{#if loading} +

+ Loading champions... +

+{:else if error} +

+ Failed to load champion data. Please refresh. +

+{:else if game} + +
+ + +
+ + {#if stats} + + {/if} + + + +

+ {game.maxGuesses > 0 + ? `${game.guesses.length} / ${game.maxGuesses} guesses` + : `${game.guesses.length} guess${game.guesses.length !== 1 ? "es" : ""}`} +

+ + + + {#if game.isOver} + + {/if} +{/if} diff --git a/src/lib/components/game-over.svelte b/src/lib/components/game-over.svelte new file mode 100644 index 0000000..031bfdc --- /dev/null +++ b/src/lib/components/game-over.svelte @@ -0,0 +1,46 @@ + + +
+

+ {isWon ? "You got it!" : "Game Over"} +

+ + {#if isWon} +

+ You found {target.name} in + {guessCount} + guess{guessCount > 1 ? "es" : ""}! +

+ {:else} +

+ The champion was {target.name} +

+ {target.name} + {/if} + + {#if onNewGame} + + {/if} +
diff --git a/src/lib/components/guess-grid.svelte b/src/lib/components/guess-grid.svelte new file mode 100644 index 0000000..bd5a112 --- /dev/null +++ b/src/lib/components/guess-grid.svelte @@ -0,0 +1,28 @@ + + +
+ +
+
Champion
+ {#each CLASSIC_ATTRIBUTES as attr (attr.key)} +
{attr.label}
+ {/each} +
+ + + {#each rows as row (row.champion.id)} + + {/each} +
diff --git a/src/lib/components/guess-row.svelte b/src/lib/components/guess-row.svelte new file mode 100644 index 0000000..7e7912c --- /dev/null +++ b/src/lib/components/guess-row.svelte @@ -0,0 +1,35 @@ + + +
+ +
+ {champion.name} + {champion.name} +
+ + + {#each results as r, i (r.key)} +
+ {r.guessValue} + {#if r.direction} + + {r.direction === "up" ? " ↑" : " ↓"} + + {/if} +
+ {/each} +
diff --git a/src/lib/components/stats-display.svelte b/src/lib/components/stats-display.svelte new file mode 100644 index 0000000..51ff7c4 --- /dev/null +++ b/src/lib/components/stats-display.svelte @@ -0,0 +1,29 @@ + + +{#if stats && stats.gamesPlayed > 0} +
+ {#each items as { value, label } (label)} +
+ {value} + {label} +
+ {/each} +
+{/if} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index bbf213e..28dbcda 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,9 +1,12 @@ - + +

LoLdle

+
From b7612c7f641520f23c402082e473e1e7f03309d3 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 25 Jul 2026 11:18:58 +0700 Subject: [PATCH 5/5] chore: remove Next.js and repoint CI at the SvelteKit layout sync-data.yml now writes static/champions.json. This is the edit that had to land with the deletion of public/: the workflow reports success regardless of which path it writes, so a miss would have left the weekly sync green while the live game 404'd on data load. deploy.yml publishes build/ and passes BASE_PATH. Adds ci.yml, which runs test, lint, and build on pull_request and pushes to main -- the repo had no PR-triggered workflow before, so 'green before merge' was previously unenforceable. Deletes app/, components/, lib/, public/, next.config.mjs and postcss.config.mjs, drops the Next and React dependencies, and swaps ESLint onto eslint-plugin-svelte. sharp and unrs-resolver left pnpm's allowBuilds because both arrived with Next and pnpm why now reports no dependents. --- .github/workflows/ci.yml | 36 + .github/workflows/deploy.yml | 4 +- .github/workflows/sync-data.yml | 8 +- .gitignore | 4 - README.md | 14 + app/globals.css | 122 - app/layout.jsx | 14 - app/page.jsx | 14 - components/champion-search.jsx | 108 - components/game-board.jsx | 154 -- components/game-over.jsx | 50 - components/guess-grid.jsx | 28 - components/guess-row.jsx | 43 - components/stats-display.jsx | 25 - eslint.config.mjs | 30 +- jsconfig.json | 6 +- lib/champion-data.js | 85 - lib/classic-mode.js | 109 - lib/game-engine.js | 196 -- next.config.mjs | 10 - package.json | 16 +- pnpm-lock.yaml | 3104 ++----------------------- pnpm-workspace.yaml | 4 - postcss.config.mjs | 7 - public/champions.json | 3803 ------------------------------- 25 files changed, 234 insertions(+), 7760 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 app/globals.css delete mode 100644 app/layout.jsx delete mode 100644 app/page.jsx delete mode 100644 components/champion-search.jsx delete mode 100644 components/game-board.jsx delete mode 100644 components/game-over.jsx delete mode 100644 components/guess-grid.jsx delete mode 100644 components/guess-row.jsx delete mode 100644 components/stats-display.jsx delete mode 100644 lib/champion-data.js delete mode 100644 lib/classic-mode.js delete mode 100644 lib/game-engine.js delete mode 100644 next.config.mjs delete mode 100644 postcss.config.mjs delete mode 100644 public/champions.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b78b2ca --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v7 + with: + node-version: '24' + cache: 'pnpm' + + - run: pnpm install --frozen-lockfile --ignore-scripts + + - run: pnpm test + + - run: pnpm lint + + # No BASE_PATH here: this checks that the app compiles. The base-path + # behaviour is verified against a real preview before release. + - run: pnpm build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9b5e3aa..2929e5c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -31,11 +31,11 @@ jobs: - run: pnpm build env: - NEXT_PUBLIC_BASE_PATH: /${{ github.event.repository.name }} + BASE_PATH: /${{ github.event.repository.name }} - uses: actions/upload-pages-artifact@v5 with: - path: out + path: build deploy: needs: build diff --git a/.github/workflows/sync-data.yml b/.github/workflows/sync-data.yml index 02c57ed..18341ce 100644 --- a/.github/workflows/sync-data.yml +++ b/.github/workflows/sync-data.yml @@ -29,20 +29,20 @@ jobs: set -euo pipefail sha=$(gh api "repos/$SOURCE_REPO/commits/main" --jq .sha) gh api "repos/$SOURCE_REPO/contents/champions.json?ref=$sha" \ - -H "Accept: application/vnd.github.raw" > public/champions.json + -H "Accept: application/vnd.github.raw" > static/champions.json echo "SOURCE_SHA=${sha:0:7}" >> "$GITHUB_ENV" - name: Commit if changed id: commit run: | set -euo pipefail - if git diff --quiet -- public/champions.json; then - echo "public/champions.json already matches loldle-data@$SOURCE_SHA" + if git diff --quiet -- static/champions.json; then + echo "static/champions.json already matches loldle-data@$SOURCE_SHA" exit 0 fi git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add public/champions.json + git add static/champions.json git commit -m "chore(data): sync champions from loldle-data@$SOURCE_SHA" git push echo "changed=true" >> "$GITHUB_OUTPUT" diff --git a/.gitignore b/.gitignore index 4b6375c..3db7444 100644 --- a/.gitignore +++ b/.gitignore @@ -18,10 +18,6 @@ chrome-dev-tools # testing /coverage -# next.js -/.next/ -/out/ - # sveltekit /.svelte-kit/ diff --git a/README.md b/README.md index 45d5ccd..f78f3e4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ LoLdle-style League of Legends daily champion guessing game — data auto-update Live: https://tiennm99.github.io/loldle/ +Built with SvelteKit and Svelte 5, styled with Tailwind CSS, and prerendered to a static site deployed on GitHub Pages. + ## Quick start ```bash @@ -11,6 +13,18 @@ pnpm install pnpm dev ``` +## Commands + +```bash +pnpm dev # dev server +pnpm build # static build into build/ +pnpm preview # serve the production build +pnpm test # unit tests +pnpm lint # eslint +``` + +Champion data lives in `static/champions.json` and is refreshed weekly by the sync workflow. + ## License Apache-2.0 — see [LICENSE](LICENSE). diff --git a/app/globals.css b/app/globals.css deleted file mode 100644 index b4d482e..0000000 --- a/app/globals.css +++ /dev/null @@ -1,122 +0,0 @@ -@import "tailwindcss"; - -:root { - --color-bg: #1a1a2e; - --color-surface: #16213e; - --color-surface-hover: #1a2745; - --color-text: #e0e0e0; - --color-text-muted: #8a8a9a; - --color-correct: #4ecb71; - --color-partial: #f4b35e; - --color-wrong: #d85b5b; - --color-header: #2a2a4a; - --color-input-bg: #0f3460; - --color-input-border: #533483; - --color-accent: #533483; - --cell-size: 90px; -} - -body { - background: var(--color-bg); - color: var(--color-text); - font-family: "Segoe UI", system-ui, -apple-system, sans-serif; - min-height: 100vh; -} - -/* Guess grid layout */ -.guess-row { - display: grid; - grid-template-columns: 130px repeat(7, var(--cell-size)); - gap: 4px; - margin-bottom: 4px; - min-width: fit-content; -} - -.guess-cell { - display: flex; - align-items: center; - justify-content: center; - text-align: center; - padding: 8px 4px; - border-radius: 4px; - font-size: 0.8rem; - font-weight: 500; - min-height: 56px; - word-break: break-word; - animation: cellReveal 0.3s ease forwards; - opacity: 0; -} - -.header-row { - margin-bottom: 8px; -} - -.header-cell { - background: var(--color-header); - color: var(--color-text-muted); - font-weight: 600; - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.5px; - opacity: 1; - animation: none; -} - -.champion-cell { - display: flex; - align-items: center; - gap: 8px; - justify-content: flex-start; - padding-left: 8px; - background: var(--color-surface); -} - -.cell-correct { - background: var(--color-correct); - color: #1a1a2e; -} - -.cell-partial { - background: var(--color-partial); - color: #1a1a2e; -} - -.cell-wrong { - background: var(--color-wrong); - color: #fff; -} - -@keyframes cellReveal { - from { - opacity: 0; - transform: scale(0.8); - } - to { - opacity: 1; - transform: scale(1); - } -} - -/* Responsive */ -@media (max-width: 768px) { - :root { - --cell-size: 72px; - } - .guess-row { - grid-template-columns: 100px repeat(7, var(--cell-size)); - } - .guess-cell { - font-size: 0.7rem; - min-height: 48px; - padding: 6px 2px; - } -} - -@media (max-width: 480px) { - :root { - --cell-size: 60px; - } - .guess-row { - grid-template-columns: 80px repeat(7, var(--cell-size)); - } -} diff --git a/app/layout.jsx b/app/layout.jsx deleted file mode 100644 index deabd41..0000000 --- a/app/layout.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import "./globals.css"; - -export const metadata = { - title: "LoLdle", - description: "Guess the League of Legends champion", -}; - -export default function RootLayout({ children }) { - return ( - - {children} - - ); -} diff --git a/app/page.jsx b/app/page.jsx deleted file mode 100644 index e650a22..0000000 --- a/app/page.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import GameBoard from "@/components/game-board"; - -export default function Home() { - return ( -
-
-

- LoLdle -

-
- -
- ); -} diff --git a/components/champion-search.jsx b/components/champion-search.jsx deleted file mode 100644 index 53fe2d0..0000000 --- a/components/champion-search.jsx +++ /dev/null @@ -1,108 +0,0 @@ -"use client"; - -import { useState, useRef, useEffect, useCallback, useMemo } from "react"; -import Image from "next/image"; -import { searchChampions, getChampionImageUrl } from "@/lib/champion-data"; - -/** Autocomplete search input for champion selection */ -export default function ChampionSearch({ excludeNames, onSelect, disabled }) { - const [query, setQuery] = useState(""); - const [activeIndex, setActiveIndex] = useState(-1); - const [isOpen, setIsOpen] = useState(false); - const inputRef = useRef(null); - const dropdownRef = useRef(null); - - // Derive matches from query - const matches = useMemo(() => { - if (!query.trim()) return []; - return searchChampions(query, excludeNames); - }, [query, excludeNames]); - - // Derive dropdown open state from matches (no effect needed) - const isVisible = isOpen && matches.length > 0; - - // Close dropdown on outside click - useEffect(() => { - function handleClick(e) { - if ( - inputRef.current && - !inputRef.current.contains(e.target) && - dropdownRef.current && - !dropdownRef.current.contains(e.target) - ) { - setIsOpen(false); - } - } - document.addEventListener("mousedown", handleClick); - return () => document.removeEventListener("mousedown", handleClick); - }, []); - - const selectChampion = useCallback( - (champion) => { - setQuery(""); - setIsOpen(false); - onSelect(champion); - }, - [onSelect], - ); - - function handleKeyDown(e) { - if (!isVisible || !matches.length) return; - - if (e.key === "ArrowDown") { - e.preventDefault(); - setActiveIndex((prev) => Math.min(prev + 1, matches.length - 1)); - } else if (e.key === "ArrowUp") { - e.preventDefault(); - setActiveIndex((prev) => Math.max(prev - 1, 0)); - } else if (e.key === "Enter") { - e.preventDefault(); - if (activeIndex >= 0 && activeIndex < matches.length) { - selectChampion(matches[activeIndex]); - } - } - } - - return ( -
- { setQuery(e.target.value); setActiveIndex(-1); setIsOpen(true); }} - onKeyDown={handleKeyDown} - disabled={disabled} - placeholder={disabled ? "Game over" : "Type a champion name..."} - autoComplete="off" - className="w-full px-4 py-3 rounded-lg bg-[var(--color-input-bg)] border-2 border-[var(--color-input-border)] text-[var(--color-text)] text-base outline-none transition-colors focus:border-[var(--color-accent)] disabled:opacity-50 disabled:cursor-not-allowed placeholder:text-[var(--color-text-muted)]" - /> - - {isVisible && ( -
- {matches.map((champion, i) => ( -
selectChampion(champion)} - className={`flex items-center gap-2.5 px-3 py-2 cursor-pointer transition-colors ${ - i === activeIndex ? "bg-[var(--color-surface-hover)]" : "hover:bg-[var(--color-surface-hover)]" - }`} - > - {champion.name} - {champion.name} -
- ))} -
- )} -
- ); -} diff --git a/components/game-board.jsx b/components/game-board.jsx deleted file mode 100644 index dcd7962..0000000 --- a/components/game-board.jsx +++ /dev/null @@ -1,154 +0,0 @@ -"use client"; - -import { useState, useEffect, useCallback, useRef } from "react"; -import { - loadChampions, - getRandomChampion, - getTodaySeed, -} from "@/lib/champion-data"; -import { - createGame, - submitGuess, - getGuessedNames, - getOrCreateUnlimitedSeed, - clearUnlimitedState, - saveUnlimitedStats, - loadUnlimitedStats, - clearExpiredCache, -} from "@/lib/game-engine"; -import { compareChampions } from "@/lib/classic-mode"; -import ChampionSearch from "./champion-search"; -import GuessGrid from "./guess-grid"; -import GameOver from "./game-over"; -import StatsDisplay from "./stats-display"; - -export default function GameBoard() { - const [mode, setMode] = useState("daily"); - const [game, setGame] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [stats, setStats] = useState(null); - const gameRef = useRef(null); - - const initGame = useCallback((gameMode) => { - const seed = gameMode === "daily" ? getTodaySeed() : getOrCreateUnlimitedSeed(); - const target = getRandomChampion(seed); - const maxGuesses = gameMode === "daily" ? 6 : 0; - - const newGame = createGame({ - target, - compareFn: compareChampions, - maxGuesses, - mode: gameMode, - seed, - }); - - gameRef.current = newGame; - setGame({ ...newGame }); - setStats(gameMode === "unlimited" ? loadUnlimitedStats() : null); - }, []); - - useEffect(() => { - loadChampions() - .then(() => { - clearExpiredCache(getTodaySeed()); - initGame("daily"); - setLoading(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - }); - }, [initGame]); - - function switchMode(newMode) { - setMode(newMode); - initGame(newMode); - } - - const handleGuess = useCallback((champion) => { - const currentGame = gameRef.current; - if (!currentGame) return; - - const updated = submitGuess(currentGame, champion); - if (!updated) return; - - gameRef.current = updated; - setGame(updated); - - if (updated.isOver && updated.mode === "unlimited") { - saveUnlimitedStats(updated); - setStats(loadUnlimitedStats()); - } - }, []); - - function handleNewGame() { - clearUnlimitedState(); - initGame("unlimited"); - } - - if (loading) { - return

Loading champions...

; - } - - if (error) { - return

Failed to load champion data. Please refresh.

; - } - - if (!game) return null; - - const excludeNames = game.guesses.map((g) => g.name); - - return ( - <> - {/* Mode toggle */} -
- - -
- - {stats && } - - - -

- {game.maxGuesses > 0 - ? `${game.guesses.length} / ${game.maxGuesses} guesses` - : `${game.guesses.length} guess${game.guesses.length !== 1 ? "es" : ""}`} -

- - - - {game.isOver && ( - - )} - - ); -} diff --git a/components/game-over.jsx b/components/game-over.jsx deleted file mode 100644 index 44d874b..0000000 --- a/components/game-over.jsx +++ /dev/null @@ -1,50 +0,0 @@ -"use client"; - -import Image from "next/image"; -import { getChampionImageUrl } from "@/lib/champion-data"; - -/** Win/loss message with optional new game button */ -export default function GameOver({ isWon, target, guessCount, onNewGame }) { - return ( -
-

- {isWon ? "You got it!" : "Game Over"} -

- - {isWon ? ( -

- You found {target.name} in{" "} - {guessCount}{" "} - guess{guessCount > 1 ? "es" : ""}! -

- ) : ( - <> -

- The champion was {target.name} -

- {target.name} - - )} - - {onNewGame && ( - - )} -
- ); -} diff --git a/components/guess-grid.jsx b/components/guess-grid.jsx deleted file mode 100644 index ef80f08..0000000 --- a/components/guess-grid.jsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; - -import { CLASSIC_ATTRIBUTES } from "@/lib/classic-mode"; -import GuessRow from "./guess-row"; - -/** Grid with header row + guess rows */ -export default function GuessGrid({ guesses, results }) { - return ( -
- {/* Header row */} -
-
Champion
- {CLASSIC_ATTRIBUTES.map((attr) => ( -
- {attr.label} -
- ))} -
- - {/* Guess rows — newest first */} - {guesses - .map((champion, i) => ( - - )) - .reverse()} -
- ); -} diff --git a/components/guess-row.jsx b/components/guess-row.jsx deleted file mode 100644 index 5ec21dc..0000000 --- a/components/guess-row.jsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; - -import Image from "next/image"; -import { getChampionImageUrl } from "@/lib/champion-data"; - -/** Single guess row with colored attribute cells */ -export default function GuessRow({ champion, results, animationDelay = 0 }) { - return ( -
- {/* Champion name + image */} -
- {champion.name} - {champion.name} -
- - {/* Attribute cells */} - {results.map((r, i) => ( -
- {r.guessValue} - {r.direction && ( - - {r.direction === "up" ? " ↑" : " ↓"} - - )} -
- ))} -
- ); -} diff --git a/components/stats-display.jsx b/components/stats-display.jsx deleted file mode 100644 index 0016e3a..0000000 --- a/components/stats-display.jsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -/** Unlimited mode stats display */ -export default function StatsDisplay({ stats }) { - if (!stats || stats.gamesPlayed === 0) return null; - - const winRate = Math.round((stats.gamesWon / stats.gamesPlayed) * 100); - - const items = [ - { value: stats.gamesPlayed, label: "Played" }, - { value: `${winRate}%`, label: "Win Rate" }, - { value: stats.gamesWon, label: "Won" }, - ]; - - return ( -
- {items.map(({ value, label }) => ( -
- {value} - {label} -
- ))} -
- ); -} diff --git a/eslint.config.mjs b/eslint.config.mjs index c36bbce..01ba4b5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,27 @@ +import js from "@eslint/js"; import { defineConfig, globalIgnores } from "eslint/config"; -import nextVitals from "eslint-config-next/core-web-vitals"; +import svelte from "eslint-plugin-svelte"; +import globals from "globals"; -const eslintConfig = defineConfig([ - ...nextVitals, - globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]), +export default defineConfig([ + globalIgnores([".svelte-kit/**", "build/**"]), + + js.configs.recommended, + ...svelte.configs.recommended, + + // App code runs in the browser. + { + languageOptions: { + globals: { ...globals.browser }, + }, + }, + + // Config files and tests run in Node. Tests keep the browser globals too, + // since the localStorage stub is installed onto globalThis. + { + files: ["*.config.js", "*.config.mjs", "test/**/*.js"], + languageOptions: { + globals: { ...globals.node }, + }, + }, ]); - -export default eslintConfig; diff --git a/jsconfig.json b/jsconfig.json index 4be8af7..50c36d8 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -4,10 +4,6 @@ "allowJs": true, "checkJs": false, "module": "esnext", - "moduleResolution": "bundler", - "paths": { - "$app/types": ["./.svelte-kit/types/index.d.ts"], - "@/*": ["./*"] - } + "moduleResolution": "bundler" } } diff --git a/lib/champion-data.js b/lib/champion-data.js deleted file mode 100644 index ee197e2..0000000 --- a/lib/champion-data.js +++ /dev/null @@ -1,85 +0,0 @@ -// Data layer: load, search, and select champions - -let champions = []; -let loadPromise = null; - -/** Fetch and parse champions data (deduplicated) */ -export async function loadChampions() { - if (champions.length > 0) return champions; - if (loadPromise) return loadPromise; - - loadPromise = (async () => { - const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; - const response = await fetch(`${basePath}/champions.json`); - if (!response.ok) { - loadPromise = null; - throw new Error(`Failed to load champions: ${response.status}`); - } - champions = await response.json(); - return champions; - })(); - - return loadPromise; -} - -/** Get all loaded champions */ -export function getAllChampions() { - return champions; -} - -/** Case-insensitive lookup by name */ -export function getChampionByName(name) { - const lower = name.toLowerCase(); - return champions.find((c) => c.name.toLowerCase() === lower) || null; -} - -/** Filter champions for autocomplete (prefix-first, then substring) */ -export function searchChampions(query, excludeNames = []) { - if (!query) return []; - const lower = query.toLowerCase(); - const excluded = new Set(excludeNames.map((n) => n.toLowerCase())); - - return champions - .filter( - (c) => - c.name.toLowerCase().includes(lower) && - !excluded.has(c.name.toLowerCase()), - ) - .sort((a, b) => { - const aStarts = a.name.toLowerCase().startsWith(lower) ? 0 : 1; - const bStarts = b.name.toLowerCase().startsWith(lower) ? 0 : 1; - return aStarts - bStarts || a.name.localeCompare(b.name); - }) - .slice(0, 8); -} - -/** Seeded random champion selection (deterministic for same seed string) */ -export function getRandomChampion(seed) { - if (!champions.length) return null; - const hash = hashString(seed); - const index = hash % champions.length; - return champions[index]; -} - -/** Simple string hash (djb2) */ -function hashString(str) { - let hash = 5381; - for (let i = 0; i < str.length; i++) { - hash = (hash * 33) ^ str.charCodeAt(i); - } - return hash >>> 0; -} - -/** Get today's local date string for daily seed */ -export function getTodaySeed() { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, "0"); - const day = String(now.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - -/** Get champion image URL from Data Dragon CDN */ -export function getChampionImageUrl(championId) { - return `https://ddragon.leagueoflegends.com/cdn/img/champion/tiles/${championId}_0.jpg`; -} diff --git a/lib/classic-mode.js b/lib/classic-mode.js deleted file mode 100644 index 2522ee2..0000000 --- a/lib/classic-mode.js +++ /dev/null @@ -1,109 +0,0 @@ -// Classic mode: compare two champions across 7 attributes - -/** Attributes displayed in order */ -export const CLASSIC_ATTRIBUTES = [ - { key: "gender", label: "Gender", type: "exact" }, - { key: "genre", label: "Genre", type: "multi" }, - { key: "attackType", label: "Range", type: "exact" }, - { key: "resource", label: "Resource", type: "exact" }, - { key: "region", label: "Region", type: "exact" }, - { key: "lane", label: "Lane", type: "multi" }, - { key: "releaseDate", label: "Year", type: "year" }, -]; - -/** Compare guess champion against target champion */ -export function compareChampions(guess, target) { - return CLASSIC_ATTRIBUTES.map((attr) => { - const guessVal = guess[attr.key] || ""; - const targetVal = target[attr.key] || ""; - - switch (attr.type) { - case "exact": - return { - ...attr, - guessValue: formatValue(attr.key, guessVal), - targetValue: formatValue(attr.key, targetVal), - result: guessVal.toLowerCase() === targetVal.toLowerCase() ? "correct" : "wrong", - }; - - case "multi": - return { - ...attr, - guessValue: formatValue(attr.key, guessVal), - targetValue: formatValue(attr.key, targetVal), - result: compareMultiValue(guessVal, targetVal), - }; - - case "year": - return { - ...attr, - guessValue: guessVal || "?", - targetValue: targetVal || "?", - ...compareYear(guessVal, targetVal), - }; - - default: - return { ...attr, guessValue: guessVal, targetValue: targetVal, result: "wrong" }; - } - }); -} - -function compareMultiValue(guessStr, targetStr) { - const guessSet = parseSet(guessStr); - const targetSet = parseSet(targetStr); - - if (guessSet.size === 0 && targetSet.size === 0) return "correct"; - if (guessSet.size === 0 || targetSet.size === 0) return "wrong"; - if (setsEqual(guessSet, targetSet)) return "correct"; - - for (const val of guessSet) { - if (targetSet.has(val)) return "partial"; - } - return "wrong"; -} - -function compareYear(guessYear, targetYear) { - const g = Number(guessYear); - const t = Number(targetYear); - - if (!g || !t) return { result: "wrong" }; - if (g === t) return { result: "correct" }; - return { result: "wrong", direction: g < t ? "up" : "down" }; -} - -function parseSet(str) { - if (!str) return new Set(); - return new Set( - str.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean), - ); -} - -function setsEqual(a, b) { - if (a.size !== b.size) return false; - for (const val of a) { - if (!b.has(val)) return false; - } - return true; -} - -function formatValue(key, value) { - if (!value) return "—"; - - switch (key) { - case "gender": - return capitalize(value); - case "attackType": - return value === "close" ? "Melee" : "Ranged"; - case "region": - return value.split("-").map(capitalize).join(" "); - case "genre": - case "lane": - return value.split(",").map((s) => capitalize(s.trim())).join(", "); - default: - return value; - } -} - -function capitalize(str) { - return str.charAt(0).toUpperCase() + str.slice(1); -} diff --git a/lib/game-engine.js b/lib/game-engine.js deleted file mode 100644 index d6de95f..0000000 --- a/lib/game-engine.js +++ /dev/null @@ -1,196 +0,0 @@ -// Mode-agnostic game state machine with localStorage persistence - -const STORAGE_KEY_PREFIX = "loldle_"; -const UNLIMITED_SEED_KEY = `${STORAGE_KEY_PREFIX}unlimited_seed`; - -/** - * Create a new game instance - * @param {Object} config - * @param {Object} config.target - Target champion to guess - * @param {Function} config.compareFn - (guess, target) => comparison results - * @param {number} config.maxGuesses - Max allowed guesses (0 = unlimited) - * @param {string} config.mode - "daily" or "unlimited" - * @param {string} config.seed - Seed string for mode - */ -export function createGame(config) { - const { target, compareFn, maxGuesses = 6, mode = "daily", seed = "" } = config; - - // Try to restore saved state - const saved = loadState(mode, seed); - if (saved && saved.targetName === target.name) { - return { - target, - compareFn, - maxGuesses, - mode, - seed, - guesses: saved.guesses, - results: saved.results, - isOver: saved.isOver, - isWon: saved.isWon, - }; - } - - return { - target, - compareFn, - maxGuesses, - mode, - seed, - guesses: [], - results: [], - isOver: false, - isWon: false, - }; -} - -/** Submit a guess and return updated game state (immutable) */ -export function submitGuess(game, champion) { - if (game.isOver) return null; - if (game.guesses.some((g) => g.name === champion.name)) return null; - - const result = game.compareFn(champion, game.target); - const guesses = [...game.guesses, champion]; - const results = [...game.results, result]; - - let isWon = false; - let isOver = false; - - if (champion.name === game.target.name) { - isWon = true; - isOver = true; - } else if (game.maxGuesses > 0 && guesses.length >= game.maxGuesses) { - isOver = true; - } - - const updated = { ...game, guesses, results, isWon, isOver }; - saveState(updated); - return updated; -} - -/** Get names of already-guessed champions */ -export function getGuessedNames(game) { - return game.guesses.map((g) => g.name); -} - -/** Get or create a persistent seed for unlimited mode */ -export function getOrCreateUnlimitedSeed() { - try { - const saved = localStorage.getItem(UNLIMITED_SEED_KEY); - if (saved) return saved; - } catch { - // Ignore - } - return createNewUnlimitedSeed(); -} - -/** Create and persist a new unlimited seed */ -function createNewUnlimitedSeed() { - const seed = `unlimited_${Date.now()}_${Math.random()}`; - try { - localStorage.setItem(UNLIMITED_SEED_KEY, seed); - } catch { - // Ignore - } - return seed; -} - -/** Clear unlimited mode saved state (for new game) */ -export function clearUnlimitedState() { - try { - localStorage.removeItem(`${STORAGE_KEY_PREFIX}unlimited_current`); - localStorage.removeItem(UNLIMITED_SEED_KEY); - } catch { - // Ignore - } -} - -/** Save unlimited mode stats */ -export function saveUnlimitedStats(game) { - if (game.mode !== "unlimited" || !game.isOver) return; - - const key = `${STORAGE_KEY_PREFIX}unlimited_stats`; - let stats; - try { - stats = JSON.parse(localStorage.getItem(key)) || createEmptyStats(); - } catch { - stats = createEmptyStats(); - } - - stats.gamesPlayed++; - if (game.isWon) { - stats.gamesWon++; - const guessCount = game.guesses.length; - stats.guessDistribution[guessCount] = (stats.guessDistribution[guessCount] || 0) + 1; - } - stats.lastPlayed = Date.now(); - - try { - localStorage.setItem(key, JSON.stringify(stats)); - } catch { - // Ignore - } -} - -/** Load unlimited stats */ -export function loadUnlimitedStats() { - try { - return JSON.parse(localStorage.getItem(`${STORAGE_KEY_PREFIX}unlimited_stats`)) || createEmptyStats(); - } catch { - return createEmptyStats(); - } -} - -function createEmptyStats() { - return { gamesPlayed: 0, gamesWon: 0, guessDistribution: {}, lastPlayed: null }; -} - -function saveState(game) { - const key = getStorageKey(game.mode, game.seed); - const data = { - targetName: game.target.name, - guesses: game.guesses, - results: game.results, - isOver: game.isOver, - isWon: game.isWon, - timestamp: Date.now(), - }; - try { - localStorage.setItem(key, JSON.stringify(data)); - } catch { - // Ignore - } -} - -function loadState(mode, seed) { - const key = getStorageKey(mode, seed); - try { - const raw = localStorage.getItem(key); - if (!raw) return null; - return JSON.parse(raw); - } catch { - return null; - } -} - -/** Remove stale daily entries from localStorage (keeps only today's) */ -export function clearExpiredCache(todaySeed) { - try { - const todayKey = `${STORAGE_KEY_PREFIX}daily_${todaySeed}`; - const keysToRemove = []; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key && key.startsWith(`${STORAGE_KEY_PREFIX}daily_`) && key !== todayKey) { - keysToRemove.push(key); - } - } - keysToRemove.forEach((key) => localStorage.removeItem(key)); - } catch { - // Ignore - } -} - -function getStorageKey(mode, seed) { - if (mode === "daily") return `${STORAGE_KEY_PREFIX}daily_${seed}`; - return `${STORAGE_KEY_PREFIX}unlimited_current`; -} diff --git a/next.config.mjs b/next.config.mjs deleted file mode 100644 index 5e38cca..0000000 --- a/next.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - output: "export", - basePath: process.env.NEXT_PUBLIC_BASE_PATH || "", - images: { - unoptimized: true, - }, -}; - -export default nextConfig; diff --git a/package.json b/package.json index 60ef9d4..732393c 100644 --- a/package.json +++ b/package.json @@ -8,25 +8,19 @@ "dev": "vite dev", "build": "vite build", "preview": "vite preview", - "lint": "eslint", + "lint": "eslint .", "test": "vitest run", - "test:watch": "vitest", - "next:dev": "next dev", - "next:build": "next build" - }, - "dependencies": { - "next": "16.2.10", - "react": "19.2.7", - "react-dom": "19.2.7" + "test:watch": "vitest" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.70.1", "@sveltejs/vite-plugin-svelte": "^7.2.0", - "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/vite": "^4.3.3", "eslint": "^9", - "eslint-config-next": "16.2.10", + "eslint-plugin-svelte": "^3.22.0", + "globals": "^17.7.0", "svelte": "^5.56.7", "tailwindcss": "^4.3.3", "vite": "^8.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9e840e..40e2d30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,41 +12,34 @@ overrides: importers: .: - dependencies: - next: - specifier: 16.2.10 - version: 16.2.10(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: - specifier: 19.2.7 - version: 19.2.7 - react-dom: - specifier: 19.2.7 - version: 19.2.7(react@19.2.7) devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@9.39.5(jiti@2.7.0)) '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0))) + version: 3.0.10(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0))) '@sveltejs/kit': specifier: ^2.70.1 - version: 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0)) + version: 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)) '@sveltejs/vite-plugin-svelte': specifier: ^7.2.0 - version: 7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)) - '@tailwindcss/postcss': - specifier: ^4.3.3 - version: 4.3.3 + version: 7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)) '@tailwindcss/vite': specifier: ^4.3.3 version: 4.3.3(vite@8.1.5(jiti@2.7.0)) eslint: specifier: ^9 version: 9.39.5(jiti@2.7.0) - eslint-config-next: - specifier: 16.2.10 - version: 16.2.10(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-svelte: + specifier: ^3.22.0 + version: 3.22.0(eslint@9.39.5(jiti@2.7.0))(svelte@5.56.7) + globals: + specifier: ^17.7.0 + version: 17.7.0 svelte: specifier: ^5.56.7 - version: 5.56.7(@typescript-eslint/types@8.59.4) + version: 5.56.7 tailwindcss: specifier: ^4.3.3 version: 4.3.3 @@ -59,109 +52,12 @@ importers: packages: - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.7': - resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.7': - resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.29.7': - resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.29.7': - resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.29.7': - resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.29.7 - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.7': - resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} - engines: {node: '>=6.9.0'} - - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} @@ -191,6 +87,15 @@ packages: resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + '@eslint/js@9.39.5': resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -223,159 +128,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -392,92 +144,12 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@16.2.10': - resolution: {integrity: sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==} - - '@next/eslint-plugin-next@16.2.10': - resolution: {integrity: sha512-Gs8D2m21VnJeFo9qvYIIqJH94frWerWYu41BprU1pLtRVF7PCQNLiFZZ3fG+iPuj3K83Cwv/rt+msLOy8Qgu3Q==} - - '@next/swc-darwin-arm64@16.2.10': - resolution: {integrity: sha512-v9IdJCa0H0mbo+8z5zwUpOk1Vj7RjkcI5uNYf5Ws1y6szf/p3Mzl9hLaST8SCt6L9h8NGnruZcd2+o0NTNwDhA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@16.2.10': - resolution: {integrity: sha512-17IS0jJRViROGmA9uGdNR8VPJpfbnaVG7E9qhso5jDLkmyd0lSDORWxbcKINzcFqzZqGwGtMSnrFRxBpuUYjLQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@16.2.10': - resolution: {integrity: sha512-GRQRsRtuciNJvB54AvvuQTiq0oZtFwa1owQqtZD8wwnGpM2L39MV22kpI72YSXLKIyY40LC66EiLFv4PiicXxg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-musl@16.2.10': - resolution: {integrity: sha512-zkN9MQYS7UQBro+FnISUq1itaQjXI9xqISzuQ+2bc921NcJ1x4yPCqrn77tVN6/dOOXaaWVX3k6/bR07pPwK+A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-gnu@16.2.10': - resolution: {integrity: sha512-iCVJnwvrPYECvA6WM/7+oo+OiTvedIKLxtCLAZP4xZR3nXa1zmzZyLPbYCmWvpd4CvMYF1EMTafd0ii3DygLvA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-musl@16.2.10': - resolution: {integrity: sha512-ov2g4H0dHY9bPoOU83m91hWT7Iq5qy13bUnyyshLU3HGR1Ownn0X9QpmDPc5iIUaahTp7f7LeGAhV4DSFtackw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-win32-arm64-msvc@16.2.10': - resolution: {integrity: sha512-DwAnhLX76HQiFFQNgWlcK+JzlnD1rZ+UK/WY0ZMI/deXpvgnesjNYrqcfo1JzBuz4Kf7o3brIBL0glI1junatA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.2.10': - resolution: {integrity: sha512-0JXq3b85Jk9Jg4ntLUbXSPvoDw3gpZou7twuKdoFG2jOw635v7+IiXfTaa0TxVMyx78pUjnrVYwLgjKfX4e6/A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@nolyfill/is-core-module@1.0.39': - resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} - engines: {node: '>=12.4.0'} - '@oxc-project/types@0.139.0': resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} @@ -582,9 +254,6 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@rtsao/scc@1.1.0': - resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -621,9 +290,6 @@ packages: svelte: ^5.46.4 vite: ^8.0.0-beta.7 || ^8.0.0 - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@tailwindcss/node@4.3.3': resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} @@ -713,17 +379,11 @@ packages: resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} engines: {node: '>= 20'} - '@tailwindcss/postcss@4.3.3': - resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - '@tailwindcss/vite@4.3.3': resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -742,191 +402,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@typescript-eslint/eslint-plugin@8.59.4': - resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.59.4 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/parser@8.59.4': - resolution: {integrity: sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/project-service@8.59.4': - resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/scope-manager@8.59.4': - resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/tsconfig-utils@8.59.4': - resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/type-utils@8.59.4': - resolution: {integrity: sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/types@8.59.4': - resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.59.4': - resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/utils@8.59.4': - resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - '@typescript-eslint/visitor-keys@8.59.4': - resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@unrs/resolver-binding-android-arm-eabi@1.12.2': - resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} - cpu: [arm] - os: [android] - - '@unrs/resolver-binding-android-arm64@1.12.2': - resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} - cpu: [arm64] - os: [android] - - '@unrs/resolver-binding-darwin-arm64@1.12.2': - resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} - cpu: [arm64] - os: [darwin] - - '@unrs/resolver-binding-darwin-x64@1.12.2': - resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} - cpu: [x64] - os: [darwin] - - '@unrs/resolver-binding-freebsd-x64@1.12.2': - resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} - cpu: [x64] - os: [freebsd] - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': - resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': - resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': - resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-arm64-musl@1.12.2': - resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': - resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-loong64-musl@1.12.2': - resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': - resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': - resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': - resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': - resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-x64-gnu@1.12.2': - resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@unrs/resolver-binding-linux-x64-musl@1.12.2': - resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@unrs/resolver-binding-openharmony-arm64@1.12.2': - resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} - cpu: [arm64] - os: [openharmony] - - '@unrs/resolver-binding-wasm32-wasi@1.12.2': - resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': - resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} - cpu: [arm64] - os: [win32] - - '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': - resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} - cpu: [ia32] - os: [win32] - - '@unrs/resolver-binding-win32-x64-msvc@1.12.2': - resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} - cpu: [x64] - os: [win32] - '@vitest/expect@4.1.10': resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} @@ -980,61 +458,10 @@ packages: resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} - - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} - - array.prototype.findlastindex@1.2.6: - resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} - - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-types-flow@0.0.8: - resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - axe-core@4.11.4: - resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} - engines: {node: '>=4'} - axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -1042,50 +469,13 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - baseline-browser-mapping@2.10.31: - resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} - engines: {node: '>=6.0.0'} - hasBin: true - brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001793: - resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} - chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -1094,9 +484,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1122,28 +509,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -1161,14 +530,6 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1176,141 +537,27 @@ packages: devalue@5.8.2: resolution: {integrity: sha512-DObPPAfdtFbXjxLqK8s2Xk9ZuWz5+ZoFEhC7J76es4GU/rEiXwHTmbImoCdyoCOcBH1UF3+Cz6Z2sYD4hyl5TA==} - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - electron-to-chromium@1.5.361: - resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.24.2: resolution: {integrity: sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==} engines: {node: '>=10.13.0'} - es-abstract@1.24.2: - resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-iterator-helpers@1.3.2: - resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} - engines: {node: '>= 0.4'} - es-module-lexer@2.3.1: resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@16.2.10: - resolution: {integrity: sha512-HSybLOY0QKf39i4FWUqPN0xWiNDi6A6UqJmZtgDkS3zMqjXTqULvj/sueXx3cdCG0mVG+qH6k5/qdegklH1d1w==} + eslint-plugin-svelte@3.22.0: + resolution: {integrity: sha512-O3qn0NePTWta+1o25dIThqeEP/hEQ3VxDK2LVO8SQ5wG9umLMvulK+m1yQ4JGOb2Pkl8IB0G1lpRV/HXDXSLTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: '>=9.0.0' - typescript: '>=3.3.1' + eslint: ^8.57.1 || ^9.0.0 || ^10.0.0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 peerDependenciesMeta: - typescript: + svelte: optional: true - eslint-import-resolver-node@0.3.10: - resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} - - eslint-import-resolver-typescript@3.10.1: - resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - eslint-plugin-import-x: '*' - peerDependenciesMeta: - eslint-plugin-import: - optional: true - eslint-plugin-import-x: - optional: true - - eslint-module-utils@2.12.1: - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - - eslint-plugin-import@2.32.0: - resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-jsx-a11y@6.10.2: - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - - eslint-plugin-react-hooks@7.1.1: - resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} - engines: {node: '>=18'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 - - eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1323,10 +570,6 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.5: resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1378,19 +621,12 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1404,10 +640,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1419,52 +651,11 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.14.0: - resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -1477,58 +668,21 @@ packages: resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} engines: {node: '>=18'} - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + engines: {node: '>=18'} graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} - engines: {node: '>= 0.4'} - - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -1537,142 +691,28 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-bun-module@2.0.0: - resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} - jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.3.0: resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1682,19 +722,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1702,12 +729,8 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - language-subtag-registry@0.3.23: - resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} @@ -1787,6 +810,10 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} @@ -1797,38 +824,12 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} - engines: {node: 18 || 20 || >=22} - minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -1841,75 +842,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-postinstall@0.3.4: - resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@16.2.10: - resolution: {integrity: sha512-2som5AVXb3kE6Yjine3/mNbBayYF58eguBWIVVUdr1y/L426xyVEgYxgBG+1QC34P2x5E+tcDup6XkuOAX3dCA==} - engines: {node: '>=20.9.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 - babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} - engines: {node: '>= 0.4'} - - node-releases@2.0.45: - resolution: {integrity: sha512-iIbHXV9eBB2nB0wa7oTsrrXq+qQt+9SIlx9AX3T96YgobtEQfis5n6TJ6vV+3QP8DwdriEAcGhARaFCu37peBg==} - engines: {node: '>=18'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - obug@2.1.4: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} @@ -1918,10 +853,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1942,19 +873,12 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -1963,9 +887,33 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.5.16' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: '>=8.5.16' + + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: '>=8.5.16' + + postcss-selector-parser@7.1.4: + resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + engines: {node: '>=4'} postcss@8.5.19: resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} @@ -1975,79 +923,19 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - react-dom@19.2.7: - resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} - peerDependencies: - react: ^19.2.7 - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} - engines: {node: '>=0.10.0'} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve@2.0.0-next.7: - resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} - engines: {node: '>= 0.4'} - hasBin: true - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.1.5: resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safe-array-concat@1.1.4: - resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} - engines: {node: '>=0.4'} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - semver@7.8.1: resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} @@ -2056,22 +944,6 @@ packages: set-cookie-parser@3.1.2: resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2080,22 +952,6 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2107,70 +963,28 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - stable-hash@0.0.5: - resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} - stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - string.prototype.includes@2.0.1: - resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} - engines: {node: '>= 0.4'} - - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + svelte-eslint-parser@1.8.0: + resolution: {integrity: sha512-mikR1qwIVy3t5WthUoAXkMwxkXvabZP9FJgdx35Ei7EbGWmctva1Pih16Koeor/bdNNq8NXHlwKGS6NkYTawLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.34.1} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true svelte@5.56.7: resolution: {integrity: sha512-5qERUZX80oQj6XrDMUmD2Uhd/cIpCPDWWKBK3ZHmyRUC9apPyamWM8xMo31mbWsIQxwG2hVoSnOJ/EcnhVkkzQ==} @@ -2202,23 +1016,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2226,50 +1027,12 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - typescript-eslint@8.59.4: - resolution: {integrity: sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - unrs-resolver@1.12.2: - resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vite@8.1.5: resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2362,22 +1125,6 @@ packages: jsdom: optional: true - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} - engines: {node: '>= 0.4'} - which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2392,8 +1139,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} @@ -2402,159 +1150,19 @@ packages: zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} - zod-validation-error@4.0.2: - resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - snapshots: - '@alloc/quick-lru@5.2.0': {} - - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.7': {} - - '@babel/core@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.7': - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.29.7': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-module-imports@7.29.7': - dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/helper-validator-option@7.29.7': {} - - '@babel/helpers@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - - '@babel/parser@7.29.3': - dependencies: - '@babel/types': 7.29.0 - - '@babel/parser@7.29.7': - dependencies: - '@babel/types': 7.29.7 - - '@babel/template@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - - '@babel/traverse@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/template': 7.29.7 - '@babel/types': 7.29.7 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@babel/types@7.29.7': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@emnapi/core@1.10.0': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - '@emnapi/core@1.11.1': dependencies: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.10.0': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 @@ -2597,6 +1205,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/js@10.0.1(eslint@9.39.5(jiti@2.7.0))': + optionalDependencies: + eslint: 9.39.5(jiti@2.7.0) + '@eslint/js@9.39.5': {} '@eslint/object-schema@2.1.7': {} @@ -2622,103 +1234,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@img/colour@1.1.0': - optional: true - - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true - - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 - optional: true - - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 - optional: true - - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 - optional: true - - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 - optional: true - - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 - optional: true - - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': - dependencies: - '@emnapi/runtime': 1.10.0 - optional: true - - '@img/sharp-win32-arm64@0.34.5': - optional: true - - '@img/sharp-win32-ia32@0.34.5': - optional: true - - '@img/sharp-win32-x64@0.34.5': - optional: true - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2738,13 +1253,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 - optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -2752,50 +1260,6 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@next/env@16.2.10': {} - - '@next/eslint-plugin-next@16.2.10': - dependencies: - fast-glob: 3.3.1 - - '@next/swc-darwin-arm64@16.2.10': - optional: true - - '@next/swc-darwin-x64@16.2.10': - optional: true - - '@next/swc-linux-arm64-gnu@16.2.10': - optional: true - - '@next/swc-linux-arm64-musl@16.2.10': - optional: true - - '@next/swc-linux-x64-gnu@16.2.10': - optional: true - - '@next/swc-linux-x64-musl@16.2.10': - optional: true - - '@next/swc-win32-arm64-msvc@16.2.10': - optional: true - - '@next/swc-win32-x64-msvc@16.2.10': - optional: true - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@nolyfill/is-core-module@1.0.39': {} - '@oxc-project/types@0.139.0': {} '@polka/url@1.0.0-next.29': {} @@ -2851,23 +1315,21 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rtsao/scc@1.1.0': {} - '@standard-schema/spec@1.1.0': {} '@sveltejs/acorn-typescript@1.0.11(acorn@8.16.0)': dependencies: acorn: 8.16.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)))': dependencies: - '@sveltejs/kit': 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0)) + '@sveltejs/kit': 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)) - '@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7(@typescript-eslint/types@8.59.4))(typescript@6.0.3)(vite@8.1.5(jiti@2.7.0))': + '@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)))(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.11(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0)) + '@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -2878,24 +1340,18 @@ snapshots: mrmime: 2.0.1 set-cookie-parser: 3.1.2 sirv: 3.0.2 - svelte: 5.56.7(@typescript-eslint/types@8.59.4) + svelte: 5.56.7 vite: 8.1.5(jiti@2.7.0) - optionalDependencies: - typescript: 6.0.3 - '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7(@typescript-eslint/types@8.59.4))(vite@8.1.5(jiti@2.7.0))': + '@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.7)(vite@8.1.5(jiti@2.7.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.4 - svelte: 5.56.7(@typescript-eslint/types@8.59.4) + svelte: 5.56.7 vite: 8.1.5(jiti@2.7.0) vitefu: 1.1.3(vite@8.1.5(jiti@2.7.0)) - '@swc/helpers@0.5.15': - dependencies: - tslib: 2.8.1 - '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 @@ -2957,14 +1413,6 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/postcss@4.3.3': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.3.3 - '@tailwindcss/oxide': 4.3.3 - postcss: 8.5.19 - tailwindcss: 4.3.3 - '@tailwindcss/vite@4.3.3(vite@8.1.5(jiti@2.7.0))': dependencies: '@tailwindcss/node': 4.3.3 @@ -2972,11 +1420,6 @@ snapshots: tailwindcss: 4.3.3 vite: 8.1.5(jiti@2.7.0) - '@tybys/wasm-util@0.10.2': - dependencies: - tslib: 2.8.1 - optional: true - '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -2995,171 +1438,8 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/json5@0.0.29': {} - '@types/trusted-types@2.0.7': {} - '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/type-utils': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.4 - eslint: 9.39.5(jiti@2.7.0) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.59.4 - debug: 4.4.3 - eslint: 9.39.5(jiti@2.7.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.59.4(typescript@6.0.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) - '@typescript-eslint/types': 8.59.4 - debug: 4.4.3 - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.59.4': - dependencies: - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/visitor-keys': 8.59.4 - - '@typescript-eslint/tsconfig-utils@8.59.4(typescript@6.0.3)': - dependencies: - typescript: 6.0.3 - - '@typescript-eslint/type-utils@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - debug: 4.4.3 - eslint: 9.39.5(jiti@2.7.0) - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.59.4': {} - - '@typescript-eslint/typescript-estree@8.59.4(typescript@6.0.3)': - dependencies: - '@typescript-eslint/project-service': 8.59.4(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@6.0.3) - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/visitor-keys': 8.59.4 - debug: 4.4.3 - minimatch: 10.2.5 - semver: 7.8.1 - tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) - eslint: 9.39.5(jiti@2.7.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.59.4': - dependencies: - '@typescript-eslint/types': 8.59.4 - eslint-visitor-keys: 5.0.1 - - '@unrs/resolver-binding-android-arm-eabi@1.12.2': - optional: true - - '@unrs/resolver-binding-android-arm64@1.12.2': - optional: true - - '@unrs/resolver-binding-darwin-arm64@1.12.2': - optional: true - - '@unrs/resolver-binding-darwin-x64@1.12.2': - optional: true - - '@unrs/resolver-binding-freebsd-x64@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-arm64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-loong64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-x64-gnu@1.12.2': - optional: true - - '@unrs/resolver-binding-linux-x64-musl@1.12.2': - optional: true - - '@unrs/resolver-binding-openharmony-arm64@1.12.2': - optional: true - - '@unrs/resolver-binding-wasm32-wasi@1.12.2': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true - - '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': - optional: true - - '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': - optional: true - - '@unrs/resolver-binding-win32-x64-msvc@1.12.2': - optional: true - '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 @@ -3222,137 +1502,19 @@ snapshots: aria-query@5.3.1: {} - aria-query@5.3.2: {} - - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - array-includes@3.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 - - array.prototype.findlast@1.2.5: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.findlastindex@1.2.6: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.flat@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - array.prototype.flatmap@1.3.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-shim-unscopables: 1.1.0 - - array.prototype.tosorted@1.1.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - assertion-error@2.0.1: {} - ast-types-flow@0.0.8: {} - - async-function@1.0.0: {} - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - axe-core@4.11.4: {} - axobject-query@4.1.0: {} balanced-match@1.0.2: {} - balanced-match@4.0.4: {} - - baseline-browser-mapping@2.10.31: {} - brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.6: - dependencies: - balanced-match: 4.0.4 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.28.2: - dependencies: - baseline-browser-mapping: 2.10.31 - caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.361 - node-releases: 2.0.45 - update-browserslist-db: 1.2.3(browserslist@4.28.2) - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.9: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - callsites@3.1.0: {} - caniuse-lite@1.0.30001793: {} - chai@6.2.2: {} chalk@4.1.2: @@ -3360,8 +1522,6 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - client-only@0.0.1: {} - clsx@2.1.1: {} color-convert@2.0.1: @@ -3382,29 +1542,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - damerau-levenshtein@1.0.8: {} - - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - debug@3.2.7: - dependencies: - ms: 2.1.3 + cssesc@3.0.0: {} debug@4.4.3: dependencies: @@ -3414,282 +1552,36 @@ snapshots: deepmerge@4.3.1: {} - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - detect-libc@2.1.2: {} devalue@5.8.2: {} - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - electron-to-chromium@1.5.361: {} - - emoji-regex@9.2.2: {} - enhanced-resolve@5.24.2: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 - es-abstract@1.24.2: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.3 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.4 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-iterator-helpers@1.3.2: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-set-tostringtag: 2.1.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - iterator.prototype: 1.1.5 - math-intrinsics: 1.1.0 - es-module-lexer@2.3.1: {} - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.3 - - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.3 - - es-to-primitive@1.3.0: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - - escalade@3.2.0: {} - escape-string-regexp@4.0.0: {} - eslint-config-next@16.2.10(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): + eslint-plugin-svelte@3.22.0(eslint@9.39.5(jiti@2.7.0))(svelte@5.56.7): dependencies: - '@next/eslint-plugin-next': 16.2.10 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) + '@jridgewell/sourcemap-codec': 1.5.5 eslint: 9.39.5(jiti@2.7.0) - eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@2.7.0)) + esutils: 2.0.3 globals: 16.4.0 - typescript-eslint: 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + known-css-properties: 0.37.0 + postcss: 8.5.19 + postcss-load-config: 3.1.4(postcss@8.5.19) + postcss-safe-parser: 7.0.1(postcss@8.5.19) + semver: 7.8.1 + svelte-eslint-parser: 1.8.0(svelte@5.56.7) optionalDependencies: - typescript: 6.0.3 + svelte: 5.56.7 transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - - eslint-import-resolver-node@0.3.10: - dependencies: - debug: 3.2.7 - is-core-module: 2.16.2 - resolve: 2.0.0-next.7 - transitivePeerDependencies: - - supports-color - - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.3 - eslint: 9.39.5(jiti@2.7.0) - get-tsconfig: 4.14.0 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.16 - unrs-resolver: 1.12.2 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - eslint: 9.39.5(jiti@2.7.0) - eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 9.39.5(jiti@2.7.0) - eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) - hasown: 2.0.3 - is-core-module: 2.16.2 - is-glob: 4.0.3 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)): - dependencies: - aria-query: 5.3.2 - array-includes: 3.1.9 - array.prototype.flatmap: 1.3.3 - ast-types-flow: 0.0.8 - axe-core: 4.11.4 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 9.39.5(jiti@2.7.0) - hasown: 2.0.3 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.5 - object.fromentries: 2.0.8 - safe-regex-test: 1.1.0 - string.prototype.includes: 2.0.1 - - eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@2.7.0)): - dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.3 - eslint: 9.39.5(jiti@2.7.0) - hermes-parser: 0.25.1 - zod: 4.4.3 - zod-validation-error: 4.0.2(zod@4.4.3) - transitivePeerDependencies: - - supports-color - - eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.3.2 - eslint: 9.39.5(jiti@2.7.0) - estraverse: 5.3.0 - hasown: 2.0.3 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.5 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.7 - semver: 6.3.1 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 + - ts-node eslint-scope@8.4.0: dependencies: @@ -3700,8 +1592,6 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint-visitor-keys@5.0.1: {} - eslint@9.39.5(jiti@2.7.0): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) @@ -3755,11 +1645,9 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.3.0(@typescript-eslint/types@8.59.4): + esrap@2.3.0: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - optionalDependencies: - '@typescript-eslint/types': 8.59.4 esrecurse@4.3.0: dependencies: @@ -3777,22 +1665,10 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-glob@3.3.1: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -3805,10 +1681,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3821,62 +1693,9 @@ snapshots: flatted@3.4.2: {} - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - fsevents@2.3.3: optional: true - function-bind@1.1.2: {} - - function.prototype.name@1.1.8: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.3 - is-callable: 1.2.7 - - functions-have-names@1.2.3: {} - - generator-function@2.0.1: {} - - gensync@1.0.0-beta.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.3 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - - get-tsconfig@4.14.0: - dependencies: - resolve-pkg-maps: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -3885,47 +1704,14 @@ snapshots: globals@16.4.0: {} - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - gopd@1.2.0: {} + globals@17.7.0: {} graceful-fs@4.2.11: {} - has-bigints@1.1.0: {} - has-flag@4.0.0: {} - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.3: - dependencies: - function-bind: 1.1.2 - - hermes-estree@0.25.1: {} - - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 - ignore@5.3.2: {} - ignore@7.0.5: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -3933,181 +1719,37 @@ snapshots: imurmurhash@0.1.4: {} - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.3 - side-channel: 1.1.0 - - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-bun-module@2.0.0: - dependencies: - semver: 7.8.1 - - is-callable@1.2.7: {} - - is-core-module@2.16.2: - dependencies: - hasown: 2.0.3 - - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - is-extglob@2.1.1: {} - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - is-map@2.0.3: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-number@7.0.0: {} - is-reference@3.0.3: dependencies: '@types/estree': 1.0.9 - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.3 - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.20 - - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - isarray@2.0.5: {} - isexe@2.0.0: {} - iterator.prototype@1.1.5: - dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 - jiti@2.7.0: {} - js-tokens@4.0.0: {} - js-yaml@4.3.0: dependencies: argparse: 2.0.1 - jsesc@3.1.0: {} - json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} - json5@1.0.2: - dependencies: - minimist: 1.2.8 - - json5@2.2.3: {} - - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 kleur@4.1.5: {} - language-subtag-registry@0.3.23: {} - - language-tags@1.0.9: - dependencies: - language-subtag-registry: 0.3.23 + known-css-properties@0.37.0: {} levn@0.4.1: dependencies: @@ -4163,6 +1805,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lilconfig@2.1.0: {} + locate-character@3.0.0: {} locate-path@6.0.0: @@ -4171,122 +1815,22 @@ snapshots: lodash.merge@4.6.2: {} - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - math-intrinsics@1.1.0: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.2 - - minimatch@10.2.5: - dependencies: - brace-expansion: 5.0.6 - minimatch@3.1.5: dependencies: brace-expansion: 1.1.14 - minimist@1.2.8: {} - mrmime@2.0.1: {} ms@2.1.3: {} nanoid@3.3.12: {} - napi-postinstall@0.3.4: {} - natural-compare@1.4.0: {} - next@16.2.10(@babel/core@7.29.7)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@next/env': 16.2.10 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.31 - caniuse-lite: 1.0.30001793 - postcss: 8.5.19 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.7) - optionalDependencies: - '@next/swc-darwin-arm64': 16.2.10 - '@next/swc-darwin-x64': 16.2.10 - '@next/swc-linux-arm64-gnu': 16.2.10 - '@next/swc-linux-arm64-musl': 16.2.10 - '@next/swc-linux-x64-gnu': 16.2.10 - '@next/swc-linux-x64-musl': 16.2.10 - '@next/swc-win32-arm64-msvc': 16.2.10 - '@next/swc-win32-x64-msvc': 16.2.10 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - - node-exports-info@1.6.0: - dependencies: - array.prototype.flatmap: 1.3.3 - es-errors: 1.3.0 - object.entries: 1.1.9 - semver: 6.3.1 - - node-releases@2.0.45: {} - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - object.entries@1.1.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.1 - - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - - object.values@1.2.1: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - obug@2.1.4: {} optionator@0.9.4: @@ -4298,12 +1842,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -4320,19 +1858,33 @@ snapshots: path-key@3.1.1: {} - path-parse@1.0.7: {} - pathe@2.0.3: {} picocolors@1.1.1: {} - picomatch@2.3.2: {} - picomatch@4.0.4: {} picomatch@4.0.5: {} - possible-typed-array-names@1.1.0: {} + postcss-load-config@3.1.4(postcss@8.5.19): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.3 + optionalDependencies: + postcss: 8.5.19 + + postcss-safe-parser@7.0.1(postcss@8.5.19): + dependencies: + postcss: 8.5.19 + + postcss-scss@4.0.9(postcss@8.5.19): + dependencies: + postcss: 8.5.19 + + postcss-selector-parser@7.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 postcss@8.5.19: dependencies: @@ -4342,60 +1894,10 @@ snapshots: prelude-ls@1.2.1: {} - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - punycode@2.3.1: {} - queue-microtask@1.2.3: {} - - react-dom@19.2.7(react@19.2.7): - dependencies: - react: 19.2.7 - scheduler: 0.27.0 - - react-is@16.13.1: {} - - react@19.2.7: {} - - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 - - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 - resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} - - resolve@2.0.0-next.7: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - node-exports-info: 1.6.0 - object-keys: 1.1.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - reusify@1.1.0: {} - rolldown@1.1.5: dependencies: '@oxc-project/types': 0.139.0 @@ -4417,125 +1919,16 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - safe-array-concat@1.1.4: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 - - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - scheduler@0.27.0: {} - - semver@6.3.1: {} - semver@7.8.1: {} set-cookie-parser@3.1.2: {} - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - - sharp@0.34.5: - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.1 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - optional: true - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - siginfo@2.0.0: {} sirv@3.0.2: @@ -4546,85 +1939,29 @@ snapshots: source-map-js@1.2.1: {} - stable-hash@0.0.5: {} - stackback@0.0.2: {} std-env@4.2.0: {} - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - - string.prototype.includes@2.0.1: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-abstract: 1.24.2 - - string.prototype.matchall@4.0.12: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.0 - - string.prototype.repeat@1.0.0: - dependencies: - define-properties: 1.2.1 - es-abstract: 1.24.2 - - string.prototype.trim@1.2.10: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.2 - es-object-atoms: 1.1.1 - has-property-descriptors: 1.0.2 - - string.prototype.trimend@1.0.9: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.9 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - strip-bom@3.0.0: {} - strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.7): - dependencies: - client-only: 0.0.1 - react: 19.2.7 - optionalDependencies: - '@babel/core': 7.29.7 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} + svelte-eslint-parser@1.8.0(svelte@5.56.7): + dependencies: + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + postcss: 8.5.19 + postcss-scss: 4.0.9(postcss@8.5.19) + postcss-selector-parser: 7.1.4 + semver: 7.8.1 + optionalDependencies: + svelte: 5.56.7 - svelte@5.56.7(@typescript-eslint/types@8.59.4): + svelte@5.56.7: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -4637,7 +1974,7 @@ snapshots: clsx: 2.1.1 devalue: 5.8.2 esm-env: 1.2.2 - esrap: 2.3.0(@typescript-eslint/types@8.59.4) + esrap: 2.3.0 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 @@ -4665,119 +2002,21 @@ snapshots: tinyrainbow@3.1.0: {} - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - totalist@3.0.1: {} - ts-api-utils@2.5.0(typescript@6.0.3): - dependencies: - typescript: 6.0.3 - - tsconfig-paths@3.15.0: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - - tslib@2.8.1: {} + tslib@2.8.1: + optional: true type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typed-array-byte-length@1.0.3: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - - typed-array-byte-offset@1.0.4: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 - - typed-array-length@1.0.7: - dependencies: - call-bind: 1.0.9 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 - - typescript-eslint@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.59.4(typescript@6.0.3) - '@typescript-eslint/utils': 8.59.4(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) - eslint: 9.39.5(jiti@2.7.0) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - - typescript@6.0.3: {} - - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 - - unrs-resolver@1.12.2: - dependencies: - napi-postinstall: 0.3.4 - optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.12.2 - '@unrs/resolver-binding-android-arm64': 1.12.2 - '@unrs/resolver-binding-darwin-arm64': 1.12.2 - '@unrs/resolver-binding-darwin-x64': 1.12.2 - '@unrs/resolver-binding-freebsd-x64': 1.12.2 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 - '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 - '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 - '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 - '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 - '@unrs/resolver-binding-linux-x64-musl': 1.12.2 - '@unrs/resolver-binding-openharmony-arm64': 1.12.2 - '@unrs/resolver-binding-wasm32-wasi': 1.12.2 - '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 - '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 - '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 - - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 - escalade: 3.2.0 - picocolors: 1.1.1 - uri-js@4.4.1: dependencies: punycode: 2.3.1 + util-deprecate@1.0.2: {} + vite@8.1.5(jiti@2.7.0): dependencies: lightningcss: 1.32.0 @@ -4818,47 +2057,6 @@ snapshots: transitivePeerDependencies: - msw - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 - - which-builtin-type@1.2.1: - dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.1.8 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.20 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 - - which-typed-array@1.1.20: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.9 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - which@2.0.2: dependencies: isexe: 2.0.0 @@ -4870,14 +2068,8 @@ snapshots: word-wrap@1.2.5: {} - yallist@3.1.1: {} + yaml@1.10.3: {} yocto-queue@0.1.0: {} zimmerframe@1.1.4: {} - - zod-validation-error@4.0.2(zod@4.4.3): - dependencies: - zod: 4.4.3 - - zod@4.4.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 33607f5..af6997c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,7 +1,3 @@ -allowBuilds: - sharp: true - unrs-resolver: true - overrides: '@babel/core': '^7.29.7' js-yaml: '^4.3.0' diff --git a/postcss.config.mjs b/postcss.config.mjs deleted file mode 100644 index 61e3684..0000000 --- a/postcss.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -const config = { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; - -export default config; diff --git a/public/champions.json b/public/champions.json deleted file mode 100644 index 9c8a61d..0000000 --- a/public/champions.json +++ /dev/null @@ -1,3803 +0,0 @@ -[ - { - "id": "Aatrox", - "name": "Aatrox", - "title": "the Darkin Blade", - "resource": "Blood Well", - "genre": "Fighter", - "skinCount": 41, - "image": { - "full": "Aatrox.png", - "sprite": "champion0.png", - "group": "champion", - "x": 0, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2013, - "region": "runeterra", - "lane": "top" - }, - { - "id": "Ahri", - "name": "Ahri", - "title": "the Nine-Tailed Fox", - "resource": "Mana", - "genre": "Mage,Assassin", - "skinCount": 95, - "image": { - "full": "Ahri.png", - "sprite": "champion0.png", - "group": "champion", - "x": 48, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2011, - "region": "ionia", - "lane": "mid" - }, - { - "id": "Akali", - "name": "Akali", - "title": "the Rogue Assassin", - "resource": "Energy", - "genre": "Assassin", - "skinCount": 101, - "image": { - "full": "Akali.png", - "sprite": "champion0.png", - "group": "champion", - "x": 96, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2010, - "region": "ionia", - "lane": "top,mid" - }, - { - "id": "Akshan", - "name": "Akshan", - "title": "the Rogue Sentinel", - "resource": "Mana", - "genre": "Marksman,Assassin", - "skinCount": 30, - "image": { - "full": "Akshan.png", - "sprite": "champion0.png", - "group": "champion", - "x": 144, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2021, - "region": "shurima", - "lane": "mid" - }, - { - "id": "Alistar", - "name": "Alistar", - "title": "the Minotaur", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 46, - "image": { - "full": "Alistar.png", - "sprite": "champion0.png", - "group": "champion", - "x": 192, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "runeterra", - "lane": "support" - }, - { - "id": "Ambessa", - "name": "Ambessa", - "title": "Matriarch of War", - "resource": "Energy", - "genre": "Fighter,Assassin", - "skinCount": 15, - "image": { - "full": "Ambessa.png", - "sprite": "champion0.png", - "group": "champion", - "x": 240, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2024, - "region": "noxus", - "lane": "mid" - }, - { - "id": "Amumu", - "name": "Amumu", - "title": "the Sad Mummy", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 62, - "image": { - "full": "Amumu.png", - "sprite": "champion0.png", - "group": "champion", - "x": 288, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "shurima", - "lane": "jungle" - }, - { - "id": "Anivia", - "name": "Anivia", - "title": "the Cryophoenix", - "resource": "Mana", - "genre": "Mage", - "skinCount": 55, - "image": { - "full": "Anivia.png", - "sprite": "champion0.png", - "group": "champion", - "x": 336, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "freljord", - "lane": "mid" - }, - { - "id": "Annie", - "name": "Annie", - "title": "the Dark Child", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 59, - "image": { - "full": "Annie.png", - "sprite": "champion0.png", - "group": "champion", - "x": 384, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "runeterra", - "lane": "mid" - }, - { - "id": "Aphelios", - "name": "Aphelios", - "title": "the Weapon of the Faithful", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 40, - "image": { - "full": "Aphelios.png", - "sprite": "champion0.png", - "group": "champion", - "x": 432, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2019, - "region": "mount-targon", - "lane": "bottom" - }, - { - "id": "Ashe", - "name": "Ashe", - "title": "the Frost Archer", - "resource": "Mana", - "genre": "Marksman,Support", - "skinCount": 93, - "image": { - "full": "Ashe.png", - "sprite": "champion0.png", - "group": "champion", - "x": 0, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "freljord", - "lane": "bottom,support" - }, - { - "id": "AurelionSol", - "name": "Aurelion Sol", - "title": "The Star Forger", - "resource": "Mana", - "genre": "Mage", - "skinCount": 38, - "image": { - "full": "AurelionSol.png", - "sprite": "champion0.png", - "group": "champion", - "x": 48, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2016, - "region": "mount-targon", - "lane": "mid" - }, - { - "id": "Aurora", - "name": "Aurora", - "title": "the Witch Between Worlds", - "resource": "Mana", - "genre": "Mage,Assassin", - "skinCount": 29, - "image": { - "full": "Aurora.png", - "sprite": "champion0.png", - "group": "champion", - "x": 96, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2024, - "region": "freljord", - "lane": "top,mid" - }, - { - "id": "Azir", - "name": "Azir", - "title": "the Emperor of the Sands", - "resource": "Mana", - "genre": "Mage,Marksman", - "skinCount": 28, - "image": { - "full": "Azir.png", - "sprite": "champion0.png", - "group": "champion", - "x": 144, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2014, - "region": "shurima", - "lane": "mid" - }, - { - "id": "Bard", - "name": "Bard", - "title": "the Wandering Caretaker", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 46, - "image": { - "full": "Bard.png", - "sprite": "champion0.png", - "group": "champion", - "x": 192, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2015, - "region": "runeterra", - "lane": "support" - }, - { - "id": "Belveth", - "name": "Bel'Veth", - "title": "the Empress of the Void", - "resource": "", - "genre": "Fighter", - "skinCount": 29, - "image": { - "full": "Belveth.png", - "sprite": "champion0.png", - "group": "champion", - "x": 240, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2022, - "region": "void", - "lane": "jungle" - }, - { - "id": "Blitzcrank", - "name": "Blitzcrank", - "title": "the Great Steam Golem", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 74, - "image": { - "full": "Blitzcrank.png", - "sprite": "champion0.png", - "group": "champion", - "x": 288, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "zaun", - "lane": "support" - }, - { - "id": "Brand", - "name": "Brand", - "title": "the Burning Vengeance", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 54, - "image": { - "full": "Brand.png", - "sprite": "champion0.png", - "group": "champion", - "x": 336, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2011, - "region": "runeterra", - "lane": "jungle,support" - }, - { - "id": "Braum", - "name": "Braum", - "title": "the Heart of the Freljord", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 61, - "image": { - "full": "Braum.png", - "sprite": "champion0.png", - "group": "champion", - "x": 384, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2014, - "region": "freljord", - "lane": "support" - }, - { - "id": "Briar", - "name": "Briar", - "title": "the Restrained Hunger", - "resource": "Fury", - "genre": "Fighter,Assassin", - "skinCount": 29, - "image": { - "full": "Briar.png", - "sprite": "champion0.png", - "group": "champion", - "x": 432, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2023, - "region": "noxus", - "lane": "jungle" - }, - { - "id": "Caitlyn", - "name": "Caitlyn", - "title": "the Sheriff of Piltover", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 63, - "image": { - "full": "Caitlyn.png", - "sprite": "champion0.png", - "group": "champion", - "x": 0, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2011, - "region": "piltover", - "lane": "bottom" - }, - { - "id": "Camille", - "name": "Camille", - "title": "the Steel Shadow", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 53, - "image": { - "full": "Camille.png", - "sprite": "champion0.png", - "group": "champion", - "x": 48, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2016, - "region": "piltover", - "lane": "top" - }, - { - "id": "Cassiopeia", - "name": "Cassiopeia", - "title": "the Serpent's Embrace", - "resource": "Mana", - "genre": "Mage", - "skinCount": 38, - "image": { - "full": "Cassiopeia.png", - "sprite": "champion0.png", - "group": "champion", - "x": 96, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2010, - "region": "noxus", - "lane": "mid" - }, - { - "id": "Chogath", - "name": "Cho'Gath", - "title": "the Terror of the Void", - "resource": "Mana", - "genre": "Tank,Mage", - "skinCount": 42, - "image": { - "full": "Chogath.png", - "sprite": "champion0.png", - "group": "champion", - "x": 144, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "divers", - "attackType": "close", - "releaseDate": 2009, - "region": "void", - "lane": "top" - }, - { - "id": "Corki", - "name": "Corki", - "title": "the Daring Bombardier", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 45, - "image": { - "full": "Corki.png", - "sprite": "champion0.png", - "group": "champion", - "x": 192, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "bandle-city", - "lane": "mid" - }, - { - "id": "Darius", - "name": "Darius", - "title": "the Hand of Noxus", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 73, - "image": { - "full": "Darius.png", - "sprite": "champion0.png", - "group": "champion", - "x": 240, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2012, - "region": "noxus", - "lane": "top" - }, - { - "id": "Diana", - "name": "Diana", - "title": "Scorn of the Moon", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 83, - "image": { - "full": "Diana.png", - "sprite": "champion0.png", - "group": "champion", - "x": 288, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2012, - "region": "mount-targon", - "lane": "jungle,mid" - }, - { - "id": "DrMundo", - "name": "Dr. Mundo", - "title": "the Madman of Zaun", - "resource": "None", - "genre": "Tank,Fighter", - "skinCount": 30, - "image": { - "full": "DrMundo.png", - "sprite": "champion0.png", - "group": "champion", - "x": 384, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "zaun", - "lane": "top" - }, - { - "id": "Draven", - "name": "Draven", - "title": "the Glorious Executioner", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 78, - "image": { - "full": "Draven.png", - "sprite": "champion0.png", - "group": "champion", - "x": 336, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2012, - "region": "noxus", - "lane": "bottom" - }, - { - "id": "Ekko", - "name": "Ekko", - "title": "the Boy Who Shattered Time", - "resource": "Mana", - "genre": "Assassin,Mage", - "skinCount": 59, - "image": { - "full": "Ekko.png", - "sprite": "champion0.png", - "group": "champion", - "x": 432, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2015, - "region": "zaun", - "lane": "jungle,mid" - }, - { - "id": "Elise", - "name": "Elise", - "title": "the Spider Queen", - "resource": "Mana", - "genre": "Assassin,Mage", - "skinCount": 36, - "image": { - "full": "Elise.png", - "sprite": "champion1.png", - "group": "champion", - "x": 0, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2012, - "region": "shadow-isles", - "lane": "jungle" - }, - { - "id": "Evelynn", - "name": "Evelynn", - "title": "Agony's Embrace", - "resource": "Mana", - "genre": "Assassin,Mage", - "skinCount": 65, - "image": { - "full": "Evelynn.png", - "sprite": "champion1.png", - "group": "champion", - "x": 48, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2009, - "region": "runeterra", - "lane": "jungle" - }, - { - "id": "Ezreal", - "name": "Ezreal", - "title": "the Prodigal Explorer", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 76, - "image": { - "full": "Ezreal.png", - "sprite": "champion1.png", - "group": "champion", - "x": 96, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2010, - "region": "piltover", - "lane": "bottom" - }, - { - "id": "Fiddlesticks", - "name": "Fiddlesticks", - "title": "the Ancient Fear", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 55, - "image": { - "full": "Fiddlesticks.png", - "sprite": "champion1.png", - "group": "champion", - "x": 144, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "runeterra", - "lane": "jungle" - }, - { - "id": "Fiora", - "name": "Fiora", - "title": "the Grand Duelist", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 100, - "image": { - "full": "Fiora.png", - "sprite": "champion1.png", - "group": "champion", - "x": 192, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2012, - "region": "demacia", - "lane": "top" - }, - { - "id": "Fizz", - "name": "Fizz", - "title": "the Tidal Trickster", - "resource": "Mana", - "genre": "Assassin,Fighter", - "skinCount": 44, - "image": { - "full": "Fizz.png", - "sprite": "champion1.png", - "group": "champion", - "x": 240, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "bilgewater", - "lane": "mid" - }, - { - "id": "Galio", - "name": "Galio", - "title": "the Colossus", - "resource": "Mana", - "genre": "Tank,Mage", - "skinCount": 54, - "image": { - "full": "Galio.png", - "sprite": "champion1.png", - "group": "champion", - "x": 288, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "demacia", - "lane": "mid" - }, - { - "id": "Gangplank", - "name": "Gangplank", - "title": "the Saltwater Scourge", - "resource": "Mana", - "genre": "Fighter", - "skinCount": 42, - "image": { - "full": "Gangplank.png", - "sprite": "champion1.png", - "group": "champion", - "x": 336, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "bilgewater", - "lane": "top" - }, - { - "id": "Garen", - "name": "Garen", - "title": "The Might of Demacia", - "resource": "None", - "genre": "Fighter,Tank", - "skinCount": 54, - "image": { - "full": "Garen.png", - "sprite": "champion1.png", - "group": "champion", - "x": 384, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "demacia", - "lane": "top" - }, - { - "id": "Gnar", - "name": "Gnar", - "title": "the Missing Link", - "resource": "Rage", - "genre": "Fighter,Tank", - "skinCount": 44, - "image": { - "full": "Gnar.png", - "sprite": "champion1.png", - "group": "champion", - "x": 432, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2014, - "region": "freljord", - "lane": "top" - }, - { - "id": "Gragas", - "name": "Gragas", - "title": "the Rabble Rouser", - "resource": "Mana", - "genre": "Fighter,Mage", - "skinCount": 48, - "image": { - "full": "Gragas.png", - "sprite": "champion1.png", - "group": "champion", - "x": 0, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "freljord", - "lane": "jungle" - }, - { - "id": "Graves", - "name": "Graves", - "title": "the Outlaw", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 54, - "image": { - "full": "Graves.png", - "sprite": "champion1.png", - "group": "champion", - "x": 48, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "bilgewater", - "lane": "jungle" - }, - { - "id": "Gwen", - "name": "Gwen", - "title": "The Hallowed Seamstress", - "resource": "Mana", - "genre": "Fighter", - "skinCount": 48, - "image": { - "full": "Gwen.png", - "sprite": "champion1.png", - "group": "champion", - "x": 96, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2021, - "region": "shadow-isles", - "lane": "top" - }, - { - "id": "Hecarim", - "name": "Hecarim", - "title": "the Shadow of War", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 50, - "image": { - "full": "Hecarim.png", - "sprite": "champion1.png", - "group": "champion", - "x": 144, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2012, - "region": "shadow-isles", - "lane": "jungle" - }, - { - "id": "Heimerdinger", - "name": "Heimerdinger", - "title": "the Revered Inventor", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 27, - "image": { - "full": "Heimerdinger.png", - "sprite": "champion1.png", - "group": "champion", - "x": 192, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "piltover", - "lane": "support" - }, - { - "id": "Hwei", - "name": "Hwei", - "title": "the Visionary", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 22, - "image": { - "full": "Hwei.png", - "sprite": "champion1.png", - "group": "champion", - "x": 240, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2023, - "region": "ionia", - "lane": "mid,support" - }, - { - "id": "Illaoi", - "name": "Illaoi", - "title": "the Kraken Priestess", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 46, - "image": { - "full": "Illaoi.png", - "sprite": "champion1.png", - "group": "champion", - "x": 288, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2015, - "region": "bilgewater", - "lane": "top" - }, - { - "id": "Irelia", - "name": "Irelia", - "title": "the Blade Dancer", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 72, - "image": { - "full": "Irelia.png", - "sprite": "champion1.png", - "group": "champion", - "x": 336, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2010, - "region": "ionia", - "lane": "top,mid" - }, - { - "id": "Ivern", - "name": "Ivern", - "title": "the Green Father", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 39, - "image": { - "full": "Ivern.png", - "sprite": "champion1.png", - "group": "champion", - "x": 384, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2016, - "region": "ionia", - "lane": "jungle" - }, - { - "id": "Janna", - "name": "Janna", - "title": "the Storm's Fury", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 67, - "image": { - "full": "Janna.png", - "sprite": "champion1.png", - "group": "champion", - "x": 432, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "zaun", - "lane": "support" - }, - { - "id": "JarvanIV", - "name": "Jarvan IV", - "title": "the Exemplar of Demacia", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 55, - "image": { - "full": "JarvanIV.png", - "sprite": "champion1.png", - "group": "champion", - "x": 0, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "demacia", - "lane": "jungle" - }, - { - "id": "Jax", - "name": "Jax", - "title": "Grandmaster at Arms", - "resource": "Mana", - "genre": "Fighter", - "skinCount": 42, - "image": { - "full": "Jax.png", - "sprite": "champion1.png", - "group": "champion", - "x": 48, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "runeterra", - "lane": "top,jungle" - }, - { - "id": "Jayce", - "name": "Jayce", - "title": "the Defender of Tomorrow", - "resource": "Mana", - "genre": "Fighter,Marksman", - "skinCount": 47, - "image": { - "full": "Jayce.png", - "sprite": "champion1.png", - "group": "champion", - "x": 96, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2012, - "region": "piltover", - "lane": "top" - }, - { - "id": "Jhin", - "name": "Jhin", - "title": "the Virtuoso", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 63, - "image": { - "full": "Jhin.png", - "sprite": "champion1.png", - "group": "champion", - "x": 144, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2016, - "region": "ionia", - "lane": "bottom" - }, - { - "id": "Jinx", - "name": "Jinx", - "title": "the Loose Cannon", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 64, - "image": { - "full": "Jinx.png", - "sprite": "champion1.png", - "group": "champion", - "x": 192, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2013, - "region": "zaun", - "lane": "bottom" - }, - { - "id": "KSante", - "name": "K'Sante", - "title": "the Pride of Nazumah", - "resource": "Mana", - "genre": "Tank,Fighter", - "skinCount": 18, - "image": { - "full": "KSante.png", - "sprite": "champion2.png", - "group": "champion", - "x": 384, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2022, - "region": "shurima", - "lane": "top" - }, - { - "id": "Kaisa", - "name": "Kai'Sa", - "title": "Daughter of the Void", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 79, - "image": { - "full": "Kaisa.png", - "sprite": "champion1.png", - "group": "champion", - "x": 240, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2018, - "region": "void", - "lane": "bottom" - }, - { - "id": "Kalista", - "name": "Kalista", - "title": "the Spear of Vengeance", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 33, - "image": { - "full": "Kalista.png", - "sprite": "champion1.png", - "group": "champion", - "x": 288, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2014, - "region": "shadow-isles", - "lane": "bottom" - }, - { - "id": "Karma", - "name": "Karma", - "title": "the Enlightened One", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 76, - "image": { - "full": "Karma.png", - "sprite": "champion1.png", - "group": "champion", - "x": 336, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2011, - "region": "ionia", - "lane": "mid,support" - }, - { - "id": "Karthus", - "name": "Karthus", - "title": "the Deathsinger", - "resource": "Mana", - "genre": "Mage", - "skinCount": 44, - "image": { - "full": "Karthus.png", - "sprite": "champion1.png", - "group": "champion", - "x": 384, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "shadow-isles", - "lane": "jungle" - }, - { - "id": "Kassadin", - "name": "Kassadin", - "title": "the Void Walker", - "resource": "Mana", - "genre": "Assassin,Mage", - "skinCount": 32, - "image": { - "full": "Kassadin.png", - "sprite": "champion1.png", - "group": "champion", - "x": 432, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "void", - "lane": "mid" - }, - { - "id": "Katarina", - "name": "Katarina", - "title": "the Sinister Blade", - "resource": "None", - "genre": "Assassin,Mage", - "skinCount": 78, - "image": { - "full": "Katarina.png", - "sprite": "champion2.png", - "group": "champion", - "x": 0, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2009, - "region": "noxus", - "lane": "mid" - }, - { - "id": "Kayle", - "name": "Kayle", - "title": "the Righteous", - "resource": "Mana", - "genre": "Mage,Marksman", - "skinCount": 88, - "image": { - "full": "Kayle.png", - "sprite": "champion2.png", - "group": "champion", - "x": 48, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2009, - "region": "demacia", - "lane": "top" - }, - { - "id": "Kayn", - "name": "Kayn", - "title": "the Shadow Reaper", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 31, - "image": { - "full": "Kayn.png", - "sprite": "champion2.png", - "group": "champion", - "x": 96, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2017, - "region": "ionia", - "lane": "jungle" - }, - { - "id": "Kennen", - "name": "Kennen", - "title": "the Heart of the Tempest", - "resource": "Energy", - "genre": "Mage", - "skinCount": 34, - "image": { - "full": "Kennen.png", - "sprite": "champion2.png", - "group": "champion", - "x": 144, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2010, - "region": "ionia", - "lane": "top" - }, - { - "id": "Khazix", - "name": "Kha'Zix", - "title": "the Voidreaver", - "resource": "Mana", - "genre": "Assassin", - "skinCount": 58, - "image": { - "full": "Khazix.png", - "sprite": "champion2.png", - "group": "champion", - "x": 192, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2012, - "region": "void", - "lane": "jungle" - }, - { - "id": "Kindred", - "name": "Kindred", - "title": "The Eternal Hunters", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 44, - "image": { - "full": "Kindred.png", - "sprite": "champion2.png", - "group": "champion", - "x": 240, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2015, - "region": "runeterra", - "lane": "jungle" - }, - { - "id": "Kled", - "name": "Kled", - "title": "the Cantankerous Cavalier", - "resource": "Courage", - "genre": "Fighter", - "skinCount": 27, - "image": { - "full": "Kled.png", - "sprite": "champion2.png", - "group": "champion", - "x": 288, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2016, - "region": "noxus", - "lane": "top" - }, - { - "id": "KogMaw", - "name": "Kog'Maw", - "title": "the Mouth of the Abyss", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 65, - "image": { - "full": "KogMaw.png", - "sprite": "champion2.png", - "group": "champion", - "x": 336, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2010, - "region": "void", - "lane": "bottom" - }, - { - "id": "Leblanc", - "name": "LeBlanc", - "title": "the Deceiver", - "resource": "Mana", - "genre": "Assassin,Mage", - "skinCount": 61, - "image": { - "full": "Leblanc.png", - "sprite": "champion2.png", - "group": "champion", - "x": 432, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2010, - "region": "noxus", - "lane": "mid" - }, - { - "id": "LeeSin", - "name": "Lee Sin", - "title": "the Blind Monk", - "resource": "Energy", - "genre": "Fighter,Assassin", - "skinCount": 80, - "image": { - "full": "LeeSin.png", - "sprite": "champion2.png", - "group": "champion", - "x": 0, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "ionia", - "lane": "jungle" - }, - { - "id": "Leona", - "name": "Leona", - "title": "the Radiant Dawn", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 71, - "image": { - "full": "Leona.png", - "sprite": "champion2.png", - "group": "champion", - "x": 48, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2011, - "region": "mount-targon", - "lane": "support" - }, - { - "id": "Lillia", - "name": "Lillia", - "title": "the Bashful Bloom", - "resource": "Mana", - "genre": "Fighter,Mage", - "skinCount": 53, - "image": { - "full": "Lillia.png", - "sprite": "champion2.png", - "group": "champion", - "x": 96, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2020, - "region": "ionia", - "lane": "jungle" - }, - { - "id": "Lissandra", - "name": "Lissandra", - "title": "the Ice Witch", - "resource": "Mana", - "genre": "Mage", - "skinCount": 52, - "image": { - "full": "Lissandra.png", - "sprite": "champion2.png", - "group": "champion", - "x": 144, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2013, - "region": "freljord", - "lane": "mid" - }, - { - "id": "Locke", - "name": "Locke", - "title": "the Ashen Exorcist", - "resource": "Mana", - "genre": "Assassin,Mage", - "skinCount": 10, - "image": { - "full": "Locke.png", - "sprite": "champion2.png", - "group": "champion", - "x": 192, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "region": "demacia" - }, - { - "id": "Lucian", - "name": "Lucian", - "title": "the Purifier", - "resource": "Mana", - "genre": "Marksman,Assassin", - "skinCount": 73, - "image": { - "full": "Lucian.png", - "sprite": "champion2.png", - "group": "champion", - "x": 240, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2013, - "region": "demacia", - "lane": "bottom" - }, - { - "id": "Lulu", - "name": "Lulu", - "title": "the Fae Sorceress", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 56, - "image": { - "full": "Lulu.png", - "sprite": "champion2.png", - "group": "champion", - "x": 288, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2012, - "region": "bandle-city", - "lane": "support" - }, - { - "id": "Lux", - "name": "Lux", - "title": "the Lady of Luminosity", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 81, - "image": { - "full": "Lux.png", - "sprite": "champion2.png", - "group": "champion", - "x": 336, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2010, - "region": "demacia", - "lane": "mid,support" - }, - { - "id": "Malphite", - "name": "Malphite", - "title": "Shard of the Monolith", - "resource": "Mana", - "genre": "Tank,Mage", - "skinCount": 57, - "image": { - "full": "Malphite.png", - "sprite": "champion2.png", - "group": "champion", - "x": 384, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "ixtal", - "lane": "top" - }, - { - "id": "Malzahar", - "name": "Malzahar", - "title": "the Prophet of the Void", - "resource": "Mana", - "genre": "Mage", - "skinCount": 58, - "image": { - "full": "Malzahar.png", - "sprite": "champion2.png", - "group": "champion", - "x": 432, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2010, - "region": "void", - "lane": "mid" - }, - { - "id": "Maokai", - "name": "Maokai", - "title": "the Twisted Treant", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 36, - "image": { - "full": "Maokai.png", - "sprite": "champion2.png", - "group": "champion", - "x": 0, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "shadow-isles", - "lane": "support" - }, - { - "id": "MasterYi", - "name": "Master Yi", - "title": "the Wuju Bladesman", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 90, - "image": { - "full": "MasterYi.png", - "sprite": "champion2.png", - "group": "champion", - "x": 48, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "ionia", - "lane": "jungle" - }, - { - "id": "Mel", - "name": "Mel", - "title": "the Soul's Reflection", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 12, - "image": { - "full": "Mel.png", - "sprite": "champion2.png", - "group": "champion", - "x": 96, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2025, - "region": "noxus", - "lane": "mid" - }, - { - "id": "Milio", - "name": "Milio", - "title": "The Gentle Flame", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 29, - "image": { - "full": "Milio.png", - "sprite": "champion2.png", - "group": "champion", - "x": 144, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2023, - "region": "ixtal", - "lane": "support" - }, - { - "id": "MissFortune", - "name": "Miss Fortune", - "title": "the Bounty Hunter", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 76, - "image": { - "full": "MissFortune.png", - "sprite": "champion2.png", - "group": "champion", - "x": 192, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2010, - "region": "bilgewater", - "lane": "bottom" - }, - { - "id": "Mordekaiser", - "name": "Mordekaiser", - "title": "the Iron Revenant", - "resource": "Shield", - "genre": "Fighter,Mage", - "skinCount": 57, - "image": { - "full": "Mordekaiser.png", - "sprite": "champion2.png", - "group": "champion", - "x": 288, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "noxus", - "lane": "top" - }, - { - "id": "Morgana", - "name": "Morgana", - "title": "the Fallen", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 82, - "image": { - "full": "Morgana.png", - "sprite": "champion2.png", - "group": "champion", - "x": 336, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2009, - "region": "demacia", - "lane": "support" - }, - { - "id": "Naafiri", - "name": "Naafiri", - "title": "the Hound of a Hundred Bites", - "resource": "Mana", - "genre": "Assassin,Fighter", - "skinCount": 38, - "image": { - "full": "Naafiri.png", - "sprite": "champion2.png", - "group": "champion", - "x": 384, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2023, - "region": "shurima", - "lane": "mid" - }, - { - "id": "Nami", - "name": "Nami", - "title": "the Tidecaller", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 68, - "image": { - "full": "Nami.png", - "sprite": "champion2.png", - "group": "champion", - "x": 432, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2012, - "region": "runeterra", - "lane": "support" - }, - { - "id": "Nasus", - "name": "Nasus", - "title": "the Curator of the Sands", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 63, - "image": { - "full": "Nasus.png", - "sprite": "champion3.png", - "group": "champion", - "x": 0, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "shurima", - "lane": "top" - }, - { - "id": "Nautilus", - "name": "Nautilus", - "title": "the Titan of the Depths", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 46, - "image": { - "full": "Nautilus.png", - "sprite": "champion3.png", - "group": "champion", - "x": 48, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2012, - "region": "bilgewater", - "lane": "support" - }, - { - "id": "Neeko", - "name": "Neeko", - "title": "the Curious Chameleon", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 50, - "image": { - "full": "Neeko.png", - "sprite": "champion3.png", - "group": "champion", - "x": 96, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2018, - "region": "ixtal", - "lane": "mid,support" - }, - { - "id": "Nidalee", - "name": "Nidalee", - "title": "the Bestial Huntress", - "resource": "Mana", - "genre": "Assassin,Mage", - "skinCount": 67, - "image": { - "full": "Nidalee.png", - "sprite": "champion3.png", - "group": "champion", - "x": 144, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "ixtal", - "lane": "jungle" - }, - { - "id": "Nilah", - "name": "Nilah", - "title": "the Joy Unbound", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 30, - "image": { - "full": "Nilah.png", - "sprite": "champion3.png", - "group": "champion", - "x": 192, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2022, - "region": "runeterra", - "lane": "bottom" - }, - { - "id": "Nocturne", - "name": "Nocturne", - "title": "the Eternal Nightmare", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 36, - "image": { - "full": "Nocturne.png", - "sprite": "champion3.png", - "group": "champion", - "x": 240, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "divers", - "attackType": "close", - "releaseDate": 2011, - "region": "runeterra", - "lane": "jungle" - }, - { - "id": "Nunu", - "name": "Nunu & Willump", - "title": "the Boy and His Yeti", - "resource": "Mana", - "genre": "Tank,Mage", - "skinCount": 54, - "image": { - "full": "Nunu.png", - "sprite": "champion3.png", - "group": "champion", - "x": 288, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "freljord", - "lane": "jungle" - }, - { - "id": "Olaf", - "name": "Olaf", - "title": "the Berserker", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 53, - "image": { - "full": "Olaf.png", - "sprite": "champion3.png", - "group": "champion", - "x": 336, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "freljord", - "lane": "top" - }, - { - "id": "Orianna", - "name": "Orianna", - "title": "the Lady of Clockwork", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 40, - "image": { - "full": "Orianna.png", - "sprite": "champion3.png", - "group": "champion", - "x": 384, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2011, - "region": "piltover", - "lane": "mid" - }, - { - "id": "Ornn", - "name": "Ornn", - "title": "The Fire below the Mountain", - "resource": "Mana", - "genre": "Tank", - "skinCount": 38, - "image": { - "full": "Ornn.png", - "sprite": "champion3.png", - "group": "champion", - "x": 432, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2017, - "region": "freljord", - "lane": "top" - }, - { - "id": "Pantheon", - "name": "Pantheon", - "title": "the Unbreakable Spear", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 47, - "image": { - "full": "Pantheon.png", - "sprite": "champion3.png", - "group": "champion", - "x": 0, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "mount-targon", - "lane": "top,support" - }, - { - "id": "Poppy", - "name": "Poppy", - "title": "Keeper of the Hammer", - "resource": "Mana", - "genre": "Tank,Fighter", - "skinCount": 51, - "image": { - "full": "Poppy.png", - "sprite": "champion3.png", - "group": "champion", - "x": 48, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2010, - "region": "demacia", - "lane": "top,jungle" - }, - { - "id": "Pyke", - "name": "Pyke", - "title": "the Bloodharbor Ripper", - "resource": "Mana", - "genre": "Support,Assassin", - "skinCount": 85, - "image": { - "full": "Pyke.png", - "sprite": "champion3.png", - "group": "champion", - "x": 96, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2018, - "region": "bilgewater", - "lane": "support" - }, - { - "id": "Qiyana", - "name": "Qiyana", - "title": "Empress of the Elements", - "resource": "Mana", - "genre": "Assassin", - "skinCount": 51, - "image": { - "full": "Qiyana.png", - "sprite": "champion3.png", - "group": "champion", - "x": 144, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2019, - "region": "ixtal", - "lane": "mid" - }, - { - "id": "Quinn", - "name": "Quinn", - "title": "Demacia's Wings", - "resource": "Mana", - "genre": "Marksman,Assassin", - "skinCount": 33, - "image": { - "full": "Quinn.png", - "sprite": "champion3.png", - "group": "champion", - "x": 192, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2013, - "region": "demacia", - "lane": "top" - }, - { - "id": "Rakan", - "name": "Rakan", - "title": "The Charmer", - "resource": "Mana", - "genre": "Support", - "skinCount": 58, - "image": { - "full": "Rakan.png", - "sprite": "champion3.png", - "group": "champion", - "x": 240, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2017, - "region": "ionia", - "lane": "support" - }, - { - "id": "Rammus", - "name": "Rammus", - "title": "the Armordillo", - "resource": "Mana", - "genre": "Tank", - "skinCount": 35, - "image": { - "full": "Rammus.png", - "sprite": "champion3.png", - "group": "champion", - "x": 288, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "shurima", - "lane": "jungle" - }, - { - "id": "RekSai", - "name": "Rek'Sai", - "title": "the Void Burrower", - "resource": "Rage", - "genre": "Fighter,Tank", - "skinCount": 36, - "image": { - "full": "RekSai.png", - "sprite": "champion3.png", - "group": "champion", - "x": 336, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2014, - "region": "void", - "lane": "jungle" - }, - { - "id": "Rell", - "name": "Rell", - "title": "the Iron Maiden", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 31, - "image": { - "full": "Rell.png", - "sprite": "champion3.png", - "group": "champion", - "x": 384, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2020, - "region": "noxus", - "lane": "support" - }, - { - "id": "Renata", - "name": "Renata Glasc", - "title": "the Chem-Baroness", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 32, - "image": { - "full": "Renata.png", - "sprite": "champion3.png", - "group": "champion", - "x": 432, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2022, - "region": "zaun", - "lane": "support" - }, - { - "id": "Renekton", - "name": "Renekton", - "title": "the Butcher of the Sands", - "resource": "Fury", - "genre": "Fighter,Tank", - "skinCount": 57, - "image": { - "full": "Renekton.png", - "sprite": "champion3.png", - "group": "champion", - "x": 0, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "shurima", - "lane": "top" - }, - { - "id": "Rengar", - "name": "Rengar", - "title": "the Pridestalker", - "resource": "Ferocity", - "genre": "Assassin,Fighter", - "skinCount": 59, - "image": { - "full": "Rengar.png", - "sprite": "champion3.png", - "group": "champion", - "x": 48, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2012, - "region": "ixtal", - "lane": "jungle" - }, - { - "id": "Riven", - "name": "Riven", - "title": "the Exile", - "resource": "None", - "genre": "Fighter,Assassin", - "skinCount": 73, - "image": { - "full": "Riven.png", - "sprite": "champion3.png", - "group": "champion", - "x": 96, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2011, - "region": "noxus", - "lane": "top" - }, - { - "id": "Rumble", - "name": "Rumble", - "title": "the Mechanized Menace", - "resource": "Heat", - "genre": "Fighter,Mage", - "skinCount": 32, - "image": { - "full": "Rumble.png", - "sprite": "champion3.png", - "group": "champion", - "x": 144, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "bandle-city", - "lane": "top" - }, - { - "id": "Ryze", - "name": "Ryze", - "title": "the Rune Mage", - "resource": "Mana", - "genre": "Mage", - "skinCount": 39, - "image": { - "full": "Ryze.png", - "sprite": "champion3.png", - "group": "champion", - "x": 192, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "runeterra", - "lane": "mid" - }, - { - "id": "Samira", - "name": "Samira", - "title": "the Desert Rose", - "resource": "Mana", - "genre": "Marksman,Assassin", - "skinCount": 42, - "image": { - "full": "Samira.png", - "sprite": "champion3.png", - "group": "champion", - "x": 240, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2020, - "region": "noxus", - "lane": "bottom" - }, - { - "id": "Sejuani", - "name": "Sejuani", - "title": "Fury of the North", - "resource": "Mana", - "genre": "Tank", - "skinCount": 57, - "image": { - "full": "Sejuani.png", - "sprite": "champion3.png", - "group": "champion", - "x": 288, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2012, - "region": "freljord", - "lane": "jungle" - }, - { - "id": "Senna", - "name": "Senna", - "title": "the Redeemer", - "resource": "Mana", - "genre": "Support,Marksman", - "skinCount": 80, - "image": { - "full": "Senna.png", - "sprite": "champion3.png", - "group": "champion", - "x": 336, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2019, - "region": "runeterra", - "lane": "bottom,support" - }, - { - "id": "Seraphine", - "name": "Seraphine", - "title": "the Starry-Eyed Songstress", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 75, - "image": { - "full": "Seraphine.png", - "sprite": "champion3.png", - "group": "champion", - "x": 384, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2020, - "region": "piltover", - "lane": "bottom,support" - }, - { - "id": "Sett", - "name": "Sett", - "title": "the Boss", - "resource": "Grit", - "genre": "Fighter,Tank", - "skinCount": 76, - "image": { - "full": "Sett.png", - "sprite": "champion3.png", - "group": "champion", - "x": 432, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2020, - "region": "ionia", - "lane": "top" - }, - { - "id": "Shaco", - "name": "Shaco", - "title": "the Demon Jester", - "resource": "Mana", - "genre": "Assassin", - "skinCount": 73, - "image": { - "full": "Shaco.png", - "sprite": "champion4.png", - "group": "champion", - "x": 0, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "runeterra", - "lane": "jungle" - }, - { - "id": "Shen", - "name": "Shen", - "title": "the Eye of Twilight", - "resource": "Energy", - "genre": "Tank", - "skinCount": 62, - "image": { - "full": "Shen.png", - "sprite": "champion4.png", - "group": "champion", - "x": 48, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "ionia", - "lane": "top" - }, - { - "id": "Shyvana", - "name": "Shyvana", - "title": "the Half-Dragon", - "resource": "Fury", - "genre": "Fighter,Tank", - "skinCount": 26, - "image": { - "full": "Shyvana.png", - "sprite": "champion4.png", - "group": "champion", - "x": 96, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2011, - "region": "demacia", - "lane": "jungle" - }, - { - "id": "Singed", - "name": "Singed", - "title": "the Mad Chemist", - "resource": "Mana", - "genre": "Tank,Mage", - "skinCount": 29, - "image": { - "full": "Singed.png", - "sprite": "champion4.png", - "group": "champion", - "x": 144, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "zaun", - "lane": "top" - }, - { - "id": "Sion", - "name": "Sion", - "title": "The Undead Juggernaut", - "resource": "Mana", - "genre": "Tank,Fighter", - "skinCount": 59, - "image": { - "full": "Sion.png", - "sprite": "champion4.png", - "group": "champion", - "x": 192, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "noxus", - "lane": "top" - }, - { - "id": "Sivir", - "name": "Sivir", - "title": "the Battle Mistress", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 81, - "image": { - "full": "Sivir.png", - "sprite": "champion4.png", - "group": "champion", - "x": 240, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "shurima", - "lane": "bottom" - }, - { - "id": "Skarner", - "name": "Skarner", - "title": "the Primordial Sovereign", - "resource": "Mana", - "genre": "Tank,Fighter", - "skinCount": 15, - "image": { - "full": "Skarner.png", - "sprite": "champion4.png", - "group": "champion", - "x": 288, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "ixtal", - "lane": "top,jungle" - }, - { - "id": "Smolder", - "name": "Smolder", - "title": "the Fiery Fledgling", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 20, - "image": { - "full": "Smolder.png", - "sprite": "champion4.png", - "group": "champion", - "x": 336, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2024, - "region": "runeterra", - "lane": "bottom" - }, - { - "id": "Sona", - "name": "Sona", - "title": "Maven of the Strings", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 77, - "image": { - "full": "Sona.png", - "sprite": "champion4.png", - "group": "champion", - "x": 384, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2010, - "region": "demacia", - "lane": "support" - }, - { - "id": "Soraka", - "name": "Soraka", - "title": "the Starchild", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 63, - "image": { - "full": "Soraka.png", - "sprite": "champion4.png", - "group": "champion", - "x": 432, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "mount-targon", - "lane": "support" - }, - { - "id": "Swain", - "name": "Swain", - "title": "the Noxian Grand General", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 51, - "image": { - "full": "Swain.png", - "sprite": "champion4.png", - "group": "champion", - "x": 0, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2010, - "region": "noxus", - "lane": "support" - }, - { - "id": "Sylas", - "name": "Sylas", - "title": "the Unshackled", - "resource": "Mana", - "genre": "Mage,Assassin", - "skinCount": 54, - "image": { - "full": "Sylas.png", - "sprite": "champion4.png", - "group": "champion", - "x": 48, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2019, - "region": "demacia", - "lane": "mid" - }, - { - "id": "Syndra", - "name": "Syndra", - "title": "the Dark Sovereign", - "resource": "Mana", - "genre": "Mage", - "skinCount": 74, - "image": { - "full": "Syndra.png", - "sprite": "champion4.png", - "group": "champion", - "x": 96, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2012, - "region": "ionia", - "lane": "mid" - }, - { - "id": "TahmKench", - "name": "Tahm Kench", - "title": "The River King", - "resource": "Mana", - "genre": "Tank,Support", - "skinCount": 47, - "image": { - "full": "TahmKench.png", - "sprite": "champion4.png", - "group": "champion", - "x": 144, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2015, - "region": "bilgewater", - "lane": "top" - }, - { - "id": "Taliyah", - "name": "Taliyah", - "title": "the Stoneweaver", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 25, - "image": { - "full": "Taliyah.png", - "sprite": "champion4.png", - "group": "champion", - "x": 192, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2016, - "region": "shurima", - "lane": "jungle" - }, - { - "id": "Talon", - "name": "Talon", - "title": "the Blade's Shadow", - "resource": "Mana", - "genre": "Assassin", - "skinCount": 60, - "image": { - "full": "Talon.png", - "sprite": "champion4.png", - "group": "champion", - "x": 240, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "noxus", - "lane": "mid" - }, - { - "id": "Taric", - "name": "Taric", - "title": "the Shield of Valoran", - "resource": "Mana", - "genre": "Support,Tank", - "skinCount": 36, - "image": { - "full": "Taric.png", - "sprite": "champion4.png", - "group": "champion", - "x": 288, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "mount-targon", - "lane": "support" - }, - { - "id": "Teemo", - "name": "Teemo", - "title": "the Swift Scout", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 63, - "image": { - "full": "Teemo.png", - "sprite": "champion4.png", - "group": "champion", - "x": 336, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "bandle-city", - "lane": "top" - }, - { - "id": "Thresh", - "name": "Thresh", - "title": "the Chain Warden", - "resource": "Mana", - "genre": "Support,Tank", - "skinCount": 68, - "image": { - "full": "Thresh.png", - "sprite": "champion4.png", - "group": "champion", - "x": 384, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2013, - "region": "shadow-isles", - "lane": "support" - }, - { - "id": "Tristana", - "name": "Tristana", - "title": "the Yordle Gunner", - "resource": "Mana", - "genre": "Marksman,Assassin", - "skinCount": 79, - "image": { - "full": "Tristana.png", - "sprite": "champion4.png", - "group": "champion", - "x": 432, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2009, - "region": "bandle-city", - "lane": "bottom" - }, - { - "id": "Trundle", - "name": "Trundle", - "title": "the Troll King", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 30, - "image": { - "full": "Trundle.png", - "sprite": "champion4.png", - "group": "champion", - "x": 0, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "freljord", - "lane": "top" - }, - { - "id": "Tryndamere", - "name": "Tryndamere", - "title": "the Barbarian King", - "resource": "Fury", - "genre": "Fighter,Assassin", - "skinCount": 47, - "image": { - "full": "Tryndamere.png", - "sprite": "champion4.png", - "group": "champion", - "x": 48, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "freljord", - "lane": "top" - }, - { - "id": "TwistedFate", - "name": "Twisted Fate", - "title": "the Card Master", - "resource": "Mana", - "genre": "Mage,Marksman", - "skinCount": 54, - "image": { - "full": "TwistedFate.png", - "sprite": "champion4.png", - "group": "champion", - "x": 96, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "bilgewater", - "lane": "mid,bottom" - }, - { - "id": "Twitch", - "name": "Twitch", - "title": "the Plague Rat", - "resource": "Mana", - "genre": "Marksman,Assassin", - "skinCount": 59, - "image": { - "full": "Twitch.png", - "sprite": "champion4.png", - "group": "champion", - "x": 144, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "zaun", - "lane": "bottom" - }, - { - "id": "Udyr", - "name": "Udyr", - "title": "the Spirit Walker", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 12, - "image": { - "full": "Udyr.png", - "sprite": "champion4.png", - "group": "champion", - "x": 192, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "freljord", - "lane": "top,jungle" - }, - { - "id": "Urgot", - "name": "Urgot", - "title": "the Dreadnought", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 41, - "image": { - "full": "Urgot.png", - "sprite": "champion4.png", - "group": "champion", - "x": 240, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "zaun", - "lane": "top" - }, - { - "id": "Varus", - "name": "Varus", - "title": "the Arrow of Retribution", - "resource": "Mana", - "genre": "Marksman,Mage", - "skinCount": 71, - "image": { - "full": "Varus.png", - "sprite": "champion4.png", - "group": "champion", - "x": 288, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2012, - "region": "ionia", - "lane": "bottom" - }, - { - "id": "Vayne", - "name": "Vayne", - "title": "the Night Hunter", - "resource": "Mana", - "genre": "Marksman,Assassin", - "skinCount": 74, - "image": { - "full": "Vayne.png", - "sprite": "champion4.png", - "group": "champion", - "x": 336, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2011, - "region": "demacia", - "lane": "bottom" - }, - { - "id": "Veigar", - "name": "Veigar", - "title": "the Tiny Master of Evil", - "resource": "Mana", - "genre": "Mage", - "skinCount": 67, - "image": { - "full": "Veigar.png", - "sprite": "champion4.png", - "group": "champion", - "x": 384, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "bandle-city", - "lane": "mid" - }, - { - "id": "Velkoz", - "name": "Vel'Koz", - "title": "the Eye of the Void", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 36, - "image": { - "full": "Velkoz.png", - "sprite": "champion4.png", - "group": "champion", - "x": 432, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2014, - "region": "void", - "lane": "support" - }, - { - "id": "Vex", - "name": "Vex", - "title": "the Gloomist", - "resource": "Mana", - "genre": "Mage", - "skinCount": 38, - "image": { - "full": "Vex.png", - "sprite": "champion5.png", - "group": "champion", - "x": 0, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2021, - "region": "shadow-isles", - "lane": "mid" - }, - { - "id": "Vi", - "name": "Vi", - "title": "the Piltover Enforcer", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 51, - "image": { - "full": "Vi.png", - "sprite": "champion5.png", - "group": "champion", - "x": 48, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2012, - "region": "piltover", - "lane": "jungle" - }, - { - "id": "Viego", - "name": "Viego", - "title": "The Ruined King", - "resource": "None", - "genre": "Fighter,Assassin", - "skinCount": 43, - "image": { - "full": "Viego.png", - "sprite": "champion5.png", - "group": "champion", - "x": 96, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2021, - "region": "shadow-isles", - "lane": "jungle" - }, - { - "id": "Viktor", - "name": "Viktor", - "title": "the Herald of the Arcane", - "resource": "Mana", - "genre": "Mage", - "skinCount": 34, - "image": { - "full": "Viktor.png", - "sprite": "champion5.png", - "group": "champion", - "x": 144, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2011, - "region": "zaun", - "lane": "mid" - }, - { - "id": "Vladimir", - "name": "Vladimir", - "title": "the Crimson Reaper", - "resource": "Crimson Rush", - "genre": "Mage,Fighter", - "skinCount": 58, - "image": { - "full": "Vladimir.png", - "sprite": "champion5.png", - "group": "champion", - "x": 192, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "noxus", - "lane": "mid" - }, - { - "id": "Volibear", - "name": "Volibear", - "title": "the Relentless Storm", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 37, - "image": { - "full": "Volibear.png", - "sprite": "champion5.png", - "group": "champion", - "x": 240, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "freljord", - "lane": "top" - }, - { - "id": "Warwick", - "name": "Warwick", - "title": "the Uncaged Wrath of Zaun", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 61, - "image": { - "full": "Warwick.png", - "sprite": "champion5.png", - "group": "champion", - "x": 288, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2009, - "region": "zaun", - "lane": "jungle" - }, - { - "id": "MonkeyKing", - "name": "Wukong", - "title": "the Monkey King", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 36, - "image": { - "full": "MonkeyKing.png", - "sprite": "champion2.png", - "group": "champion", - "x": 240, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "ionia", - "lane": "jungle" - }, - { - "id": "Xayah", - "name": "Xayah", - "title": "the Rebel", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 65, - "image": { - "full": "Xayah.png", - "sprite": "champion5.png", - "group": "champion", - "x": 336, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2017, - "region": "ionia", - "lane": "bottom" - }, - { - "id": "Xerath", - "name": "Xerath", - "title": "the Magus Ascendant", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 33, - "image": { - "full": "Xerath.png", - "sprite": "champion5.png", - "group": "champion", - "x": 384, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2011, - "region": "shurima", - "lane": "mid,support" - }, - { - "id": "XinZhao", - "name": "Xin Zhao", - "title": "the Seneschal of Demacia", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 59, - "image": { - "full": "XinZhao.png", - "sprite": "champion5.png", - "group": "champion", - "x": 432, - "y": 0, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2010, - "region": "demacia", - "lane": "jungle" - }, - { - "id": "Yasuo", - "name": "Yasuo", - "title": "the Unforgiven", - "resource": "Flow", - "genre": "Fighter,Assassin", - "skinCount": 97, - "image": { - "full": "Yasuo.png", - "sprite": "champion5.png", - "group": "champion", - "x": 0, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2013, - "region": "ionia", - "lane": "top,mid,bottom" - }, - { - "id": "Yone", - "name": "Yone", - "title": "the Unforgotten", - "resource": "Flow", - "genre": "Fighter,Assassin", - "skinCount": 76, - "image": { - "full": "Yone.png", - "sprite": "champion5.png", - "group": "champion", - "x": 48, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2020, - "region": "ionia", - "lane": "top,mid" - }, - { - "id": "Yorick", - "name": "Yorick", - "title": "Shepherd of Souls", - "resource": "Mana", - "genre": "Fighter,Tank", - "skinCount": 59, - "image": { - "full": "Yorick.png", - "sprite": "champion5.png", - "group": "champion", - "x": 96, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2011, - "region": "shadow-isles", - "lane": "top" - }, - { - "id": "Yunara", - "name": "Yunara", - "title": "the Unbroken Faith", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 17, - "image": { - "full": "Yunara.png", - "sprite": "champion5.png", - "group": "champion", - "x": 144, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2026, - "region": "ionia" - }, - { - "id": "Yuumi", - "name": "Yuumi", - "title": "the Magical Cat", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 79, - "image": { - "full": "Yuumi.png", - "sprite": "champion5.png", - "group": "champion", - "x": 192, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "close", - "releaseDate": 2019, - "region": "bandle-city", - "lane": "support" - }, - { - "id": "Zaahen", - "name": "Zaahen", - "title": "The Unsundered", - "resource": "Mana", - "genre": "Fighter,Assassin", - "skinCount": 10, - "image": { - "full": "Zaahen.png", - "sprite": "champion5.png", - "group": "champion", - "x": 240, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "region": "runeterra" - }, - { - "id": "Zac", - "name": "Zac", - "title": "the Secret Weapon", - "resource": "None", - "genre": "Tank,Fighter", - "skinCount": 42, - "image": { - "full": "Zac.png", - "sprite": "champion5.png", - "group": "champion", - "x": 288, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2013, - "region": "zaun", - "lane": "jungle" - }, - { - "id": "Zed", - "name": "Zed", - "title": "the Master of Shadows", - "resource": "Energy", - "genre": "Assassin", - "skinCount": 71, - "image": { - "full": "Zed.png", - "sprite": "champion5.png", - "group": "champion", - "x": 336, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "close", - "releaseDate": 2012, - "region": "ionia", - "lane": "mid" - }, - { - "id": "Zeri", - "name": "Zeri", - "title": "The Spark of Zaun", - "resource": "Mana", - "genre": "Marksman", - "skinCount": 47, - "image": { - "full": "Zeri.png", - "sprite": "champion5.png", - "group": "champion", - "x": 384, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2022, - "region": "zaun", - "lane": "bottom" - }, - { - "id": "Ziggs", - "name": "Ziggs", - "title": "the Hexplosives Expert", - "resource": "Mana", - "genre": "Mage", - "skinCount": 52, - "image": { - "full": "Ziggs.png", - "sprite": "champion5.png", - "group": "champion", - "x": 432, - "y": 48, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2012, - "region": "zaun", - "lane": "bottom" - }, - { - "id": "Zilean", - "name": "Zilean", - "title": "the Chronokeeper", - "resource": "Mana", - "genre": "Support,Mage", - "skinCount": 33, - "image": { - "full": "Zilean.png", - "sprite": "champion5.png", - "group": "champion", - "x": 0, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "male", - "attackType": "range", - "releaseDate": 2009, - "region": "runeterra", - "lane": "support" - }, - { - "id": "Zoe", - "name": "Zoe", - "title": "the Aspect of Twilight", - "resource": "Mana", - "genre": "Mage", - "skinCount": 45, - "image": { - "full": "Zoe.png", - "sprite": "champion5.png", - "group": "champion", - "x": 48, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2017, - "region": "mount-targon", - "lane": "mid" - }, - { - "id": "Zyra", - "name": "Zyra", - "title": "Rise of the Thorns", - "resource": "Mana", - "genre": "Mage,Support", - "skinCount": 74, - "image": { - "full": "Zyra.png", - "sprite": "champion5.png", - "group": "champion", - "x": 96, - "y": 96, - "w": 48, - "h": 48 - }, - "gender": "female", - "attackType": "range", - "releaseDate": 2012, - "region": "ixtal", - "lane": "support" - } -]