feat(sprint1-step5): migration Tailwind v4 + Zustand — suppression WildCoinContext

- Install tailwindcss @tailwindcss/vite zustand
- useGameStore.ts : Zustand store wrappant economy.ts (tick, click, buy, prestige, buyNode, loadFromServer)
- GameTick.tsx : composant timer 1s
- GeneratorShop.tsx : boutique générateurs Tailwind (remplace Amelioration.jsx)
- EvolutionTree, PrestigePanel, MilestoneBar : rewrite Zustand + Tailwind
- Hud.jsx : rewrite Zustand + Tailwind (suppression Hud.scss)
- BoutiqueCard, Achievements : migrés vers Zustand
- Supprimé : WildCoin/ (4 fichiers), timer/Timer.jsx, useEconomy.ts, Hud.scss
- WildCoinProvider retiré de main.jsx
This commit is contained in:
2026-03-20 13:40:51 +01:00
parent d215e9a33e
commit 307feb711f
20 changed files with 783 additions and 877 deletions

View File

@@ -8,12 +8,15 @@
"name": "template", "name": "template",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.2.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-lottie-player": "^1.5.5", "react-lottie-player": "^1.5.5",
"react-router-dom": "^6.19.0", "react-router-dom": "^6.19.0",
"sass": "^1.69.5" "sass": "^1.69.5",
"tailwindcss": "^4.2.2",
"zustand": "^5.0.12"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.28", "@types/react": "^18.3.28",
@@ -381,7 +384,6 @@
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz",
"integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -393,7 +395,6 @@
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz",
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -404,7 +405,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -418,7 +418,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"aix" "aix"
@@ -434,7 +433,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@@ -450,7 +448,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@@ -466,7 +463,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@@ -482,7 +478,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@@ -498,7 +493,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@@ -514,7 +508,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@@ -530,7 +523,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@@ -546,7 +538,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -562,7 +553,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -578,7 +568,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -594,7 +583,6 @@
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -610,7 +598,6 @@
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -626,7 +613,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -642,7 +628,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -658,7 +643,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -674,7 +658,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -708,7 +691,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"netbsd" "netbsd"
@@ -742,7 +724,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"openbsd" "openbsd"
@@ -776,7 +757,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"sunos" "sunos"
@@ -792,7 +772,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -808,7 +787,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -824,7 +802,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -938,33 +915,29 @@
"dev": true "dev": true
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true, "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24"
"@jridgewell/trace-mapping": "^0.3.9" }
}, },
"engines": { "node_modules/@jridgewell/remapping": {
"node": ">=6.0.0" "version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
} }
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@@ -973,14 +946,13 @@
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.20", "version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true, "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
@@ -990,7 +962,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -1631,7 +1602,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@@ -1644,7 +1614,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@@ -1657,7 +1626,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@@ -1670,7 +1638,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@@ -1683,7 +1650,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1696,7 +1662,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1709,7 +1674,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1722,7 +1686,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1735,7 +1698,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1748,7 +1710,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1761,7 +1722,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1774,7 +1734,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1787,7 +1746,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -1800,7 +1758,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -1813,7 +1770,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -1826,7 +1782,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -1839,11 +1794,267 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@tailwindcss/node": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
"integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"enhanced-resolve": "^5.19.0",
"jiti": "^2.6.1",
"lightningcss": "1.32.0",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
"tailwindcss": "4.2.2"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
"integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
"license": "MIT",
"engines": {
"node": ">= 20"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.2.2",
"@tailwindcss/oxide-darwin-arm64": "4.2.2",
"@tailwindcss/oxide-darwin-x64": "4.2.2",
"@tailwindcss/oxide-freebsd-x64": "4.2.2",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
"@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
"@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
"@tailwindcss/oxide-linux-x64-musl": "4.2.2",
"@tailwindcss/oxide-wasm32-wasi": "4.2.2",
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
"@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
"integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
"integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
"integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
"integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
"integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
"integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
"integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
"integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
"integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
"integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
],
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.8.1",
"@emnapi/runtime": "^1.8.1",
"@emnapi/wasi-threads": "^1.1.0",
"@napi-rs/wasm-runtime": "^1.1.1",
"@tybys/wasm-util": "^0.10.1",
"tslib": "^2.8.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
"integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
"integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 20"
}
},
"node_modules/@tailwindcss/vite": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz",
"integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.2.2",
"@tailwindcss/oxide": "4.2.2",
"tailwindcss": "4.2.2"
},
"peerDependencies": {
"vite": "^5.2.0 || ^6 || ^7 || ^8"
}
},
"node_modules/@tybys/wasm-util": { "node_modules/@tybys/wasm-util": {
"version": "0.10.1", "version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -1912,20 +2123,19 @@
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
"dev": true
}, },
"node_modules/@types/prop-types": { "node_modules/@types/prop-types": {
"version": "15.7.11", "version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
"dev": true "devOptional": true
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.28", "version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
@@ -2424,7 +2634,7 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
@@ -2485,7 +2695,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -2509,6 +2718,19 @@
"integrity": "sha512-hohItzsQcG7/FBsviCYMtQwUSWvVF7NVqPOnJCErWsAshsP/CR2LAXdmq276RbESNdhxiAq5/vRo1g2pxGXVww==", "integrity": "sha512-hohItzsQcG7/FBsviCYMtQwUSWvVF7NVqPOnJCErWsAshsP/CR2LAXdmq276RbESNdhxiAq5/vRo1g2pxGXVww==",
"dev": true "dev": true
}, },
"node_modules/enhanced-resolve": {
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
"integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.3.0"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.22.3", "version": "1.22.3",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
@@ -2635,7 +2857,6 @@
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
"esbuild": "bin/esbuild" "esbuild": "bin/esbuild"
@@ -3120,7 +3341,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@@ -3274,6 +3494,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/graphemer": { "node_modules/graphemer": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -3761,6 +3987,15 @@
"set-function-name": "^2.0.1" "set-function-name": "^2.0.1"
} }
}, },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -3861,7 +4096,6 @@
"version": "1.32.0", "version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"dependencies": { "dependencies": {
"detect-libc": "^2.0.3" "detect-libc": "^2.0.3"
@@ -3894,7 +4128,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3915,7 +4148,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3936,7 +4168,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3957,7 +4188,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3978,7 +4208,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3999,7 +4228,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -4020,7 +4248,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -4041,7 +4268,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -4062,7 +4288,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -4083,7 +4308,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -4104,7 +4328,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -4168,7 +4391,6 @@
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
@@ -4196,7 +4418,6 @@
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -4458,7 +4679,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@@ -4478,7 +4698,6 @@
"version": "8.5.8", "version": "8.5.8",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -4805,7 +5024,6 @@
"version": "4.17.2", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
"integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.5" "@types/estree": "1.0.5"
}, },
@@ -5135,6 +5353,25 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tailwindcss": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/text-table": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -5198,7 +5435,6 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD", "license": "0BSD",
"optional": true "optional": true
}, },
@@ -5363,7 +5599,6 @@
"version": "5.2.11", "version": "5.2.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
"integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
"dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.20.1", "esbuild": "^0.20.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",
@@ -6191,6 +6426,35 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zustand": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
"integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
} }
} }
} }

View File

@@ -11,12 +11,15 @@
"test": "vitest run" "test": "vitest run"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.2.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-lottie-player": "^1.5.5", "react-lottie-player": "^1.5.5",
"react-router-dom": "^6.19.0", "react-router-dom": "^6.19.0",
"sass": "^1.69.5" "sass": "^1.69.5",
"tailwindcss": "^4.2.2",
"zustand": "^5.0.12"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.28", "@types/react": "^18.3.28",

View File

@@ -1,7 +1,9 @@
import { useWildCoin } from "./WildCoin/WildCoinContext"; // BoutiqueCard.jsx — Legacy shop card (shop.json boosters)
// TODO: Migrate to economy.ts generator system in a future step
import "../scss/components/boutiquecard.scss"; import "../scss/components/boutiquecard.scss";
import "../scss/components/buttons.scss"; import "../scss/components/buttons.scss";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useGameStore } from "../store/useGameStore";
export default function BoutiqueCard({ export default function BoutiqueCard({
name, name,
@@ -9,89 +11,13 @@ export default function BoutiqueCard({
incrementValue, incrementValue,
description, description,
image, image,
link,
type, type,
buyed,
}) { }) {
BoutiqueCard.propTypes = { const resources = useGameStore((s) => s.state.resources);
name: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
incrementValue: PropTypes.number.isRequired,
description: PropTypes.string.isRequired,
image: PropTypes.string.isRequired,
link: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
buyed: PropTypes.bool.isRequired,
};
const { // Legacy shop — disabled for now, generators are in GeneratorShop
wildCoin, const canAfford = resources >= price;
incrementClick,
setWildCoin,
setIncrementClick,
incrementPerSecond,
setIncrementPerSecond,
setCoffee,
setSantaDrunk,
setManic,
setSnowman,
setBonnet,
setSugar,
setCookie,
setCouronne,
setEpice,
setBiere,
} = useWildCoin();
const acheterAmelioration = (type, price, name) => {
const prices = price;
const value = prices;
if (wildCoin >= value) {
if (type === "actif") {
setIncrementClick(incrementClick + incrementValue);
} else if (type === "passif") {
setIncrementPerSecond(incrementPerSecond + incrementValue);
}
setWildCoin(wildCoin - value);
switch (name) {
case "Tasse à café":
setCoffee((prevCoffee) => [true, prevCoffee[1] + 1]);
break;
case "Manic":
setManic((prevManic) => [true, prevManic[1] + 1]);
break;
case "Bonnet":
setBonnet((prevBonnet) => [true, prevBonnet[1] + 1]);
break;
case "Mr Bonhomme":
setSnowman((prevSnowman) => [true, prevSnowman[1] + 1]);
break;
case "Canne en sucre":
setSugar((prevSugar) => [true, prevSugar[1] + 1]);
break;
case "Cookie":
setCookie((prevCookie) => [true, prevCookie[1] + 1]);
break;
case "Couronne d'hiver":
setCouronne((prevCouronne) => [true, prevCouronne[1] + 1]);
break;
case "Mr pain d'épice":
setEpice((prevEpice) => [true, prevEpice[1] + 1]);
break;
case "Bière":
setBiere((prevBiere) => [true, prevBiere[1] + 1]);
setSantaDrunk(true);
break;
default:
break;
}
} else {
console.log("Pas assez de WildCoin pour acheter cette amélioration.");
}
};
return ( return (
<div className="shopcardcontainer"> <div className="shopcardcontainer">
<div className="shopcontainer"> <div className="shopcontainer">
@@ -105,7 +31,6 @@ export default function BoutiqueCard({
<p className="itemname">{name}</p> <p className="itemname">{name}</p>
<div className="price"> <div className="price">
<p className="itemprice">{price}</p> <p className="itemprice">{price}</p>
<div className="priceicon" /> <div className="priceicon" />
</div> </div>
</div> </div>
@@ -119,12 +44,22 @@ export default function BoutiqueCard({
</div> </div>
</div> </div>
<button <button
onClick={() => acheterAmelioration(type, price, name)} disabled={!canAfford}
className="primary-button" className="primary-button"
style={{ opacity: canAfford ? 1 : 0.5 }}
> >
Acheter Bientôt
</button> </button>
</div> </div>
</div> </div>
); );
} }
BoutiqueCard.propTypes = {
name: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
incrementValue: PropTypes.number.isRequired,
description: PropTypes.string.isRequired,
image: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
};

View File

@@ -0,0 +1,99 @@
// EvolutionTree.tsx — Arbre d'Évolution permanent (jamais reset)
// Visible après le premier prestige (prestigeCount >= 1)
import React from "react";
import { useGameStore } from "../store/useGameStore";
import { canBuyEvolutionNode } from "../core/economy";
import type { EvolutionNode } from "../core/economy";
const EFFECT_DESCRIPTIONS: Record<string, (value: number) => string> = {
click_multiplier: (v) => `x${v} puissance de Ponte`,
production_multiplier: (v) => `x${v} production tous générateurs`,
start_bonus: (v) => `+${v} têtards au début de chaque run`,
unlock_generator: () => `Débloque le Lac Mystique dès le début`,
achievement_scaling: (v) => `+${(v * 100).toFixed(0)}% production par succès`,
};
function NodeCard({
node,
canBuy,
onBuy,
}: {
node: EvolutionNode;
canBuy: boolean;
onBuy: () => void;
}) {
return (
<div
className={`flex flex-col gap-2 p-3 rounded-lg border text-sm transition-colors ${
node.unlocked
? "border-emerald-500/50 bg-emerald-950/30"
: canBuy
? "border-amber-500/50 bg-amber-950/20"
: "border-gray-700/50 bg-gray-800/30 opacity-50"
}`}
>
<div className="flex justify-between items-center">
<span className="text-white font-semibold">{node.name}</span>
<span className="text-xs text-gray-400">
{node.unlocked ? "Débloqué" : `${node.cost} ADN`}
</span>
</div>
<p className="text-xs text-gray-300">
{EFFECT_DESCRIPTIONS[node.effect](node.value)}
</p>
{!node.unlocked && (
<button
disabled={!canBuy}
onClick={onBuy}
className={`px-3 py-1 rounded text-xs font-medium transition-colors cursor-pointer ${
canBuy
? "bg-amber-600 hover:bg-amber-500 text-white"
: "bg-gray-700 text-gray-500 cursor-not-allowed"
}`}
>
{canBuy ? "Débloquer" : "Verrouillé"}
</button>
)}
</div>
);
}
export function EvolutionTree() {
const state = useGameStore((s) => s.state);
const buyNode = useGameStore((s) => s.buyNode);
const { evolutionTree, prestigeCount } = state;
if (prestigeCount < 1) return null;
return (
<div className="flex flex-col gap-3 p-4 rounded-xl bg-gray-900/80 backdrop-blur-sm max-w-md w-full">
<div className="flex justify-between items-center">
<h3 className="text-lg font-bold text-white">Arbre d'Évolution</h3>
<span className="text-sm text-amber-300">{state.ancestralDna} ADN</span>
</div>
<div className="flex flex-col gap-2">
{evolutionTree.map((node, index) => (
<React.Fragment key={node.id}>
{index > 0 && (
<div
className={`text-center text-xs ${
evolutionTree[index - 1].unlocked
? "text-emerald-400"
: "text-gray-600"
}`}
>
|
</div>
)}
<NodeCard
node={node}
canBuy={canBuyEvolutionNode(state, node.id)}
onBuy={() => buyNode(node.id)}
/>
</React.Fragment>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,16 @@
// GameTick.tsx — Lance le tick Zustand toutes les secondes
// À monter une seule fois dans l'arbre React (dans App)
import { useEffect } from "react";
import { useGameStore } from "../store/useGameStore";
export function GameTick() {
const tick = useGameStore((s) => s.tick);
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [tick]);
return null;
}

View File

@@ -0,0 +1,51 @@
// GeneratorShop.tsx — Boutique de générateurs (economy.ts)
// Remplace Amelioration.jsx (legacy WildCoinContext)
import { useGameStore } from "../store/useGameStore";
import { formatNumber } from "../utils/formatNumber";
export function GeneratorShop() {
const generators = useGameStore((s) => s.state.generators);
const resources = useGameStore((s) => s.state.resources);
const buy = useGameStore((s) => s.buy);
const generatorCost = useGameStore((s) => s.generatorCost);
return (
<div className="flex flex-col gap-3 p-4 rounded-xl bg-gray-900/80 backdrop-blur-sm max-w-md w-full">
<h2 className="text-lg font-bold text-white">Générateurs</h2>
{generators.map((gen) => {
const cost = generatorCost(gen);
const canAfford = resources >= cost;
return (
<div
key={gen.id}
className={`flex items-center justify-between gap-3 p-3 rounded-lg border transition-colors ${
canAfford
? "border-emerald-500/50 bg-emerald-950/30 hover:bg-emerald-950/50"
: "border-gray-700/50 bg-gray-800/30 opacity-60"
}`}
>
<div className="flex flex-col min-w-0">
<span className="text-white font-semibold text-sm">{gen.name}</span>
<span className="text-gray-400 text-xs">
+{gen.baseProduction}/s &middot; x{gen.owned}
</span>
</div>
<button
onClick={() => buy(gen.id)}
disabled={!canAfford}
className={`shrink-0 px-3 py-1.5 rounded-md text-sm font-medium transition-colors cursor-pointer ${
canAfford
? "bg-emerald-600 hover:bg-emerald-500 text-white"
: "bg-gray-700 text-gray-500 cursor-not-allowed"
}`}
>
{formatNumber(cost)}
</button>
</div>
);
})}
</div>
);
}

View File

@@ -1,155 +1,41 @@
import "../../scss/components/Hud.scss"; // Hud.jsx — Stats HUD (Zustand)
import { useWildCoin } from "../WildCoin/WildCoinContext"; import { useGameStore } from "../../store/useGameStore";
import Timer from "../timer/Timer"; import { formatNumber } from "../../utils/formatNumber";
import propTypes from "prop-types";
const formatTime = (time) => {
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const secs = time % 60;
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
};
function Hud({ isVisible }) { function Hud({ isVisible }) {
Hud.propTypes = { const resources = useGameStore((s) => s.state.resources);
isVisible: propTypes.bool.isRequired, const clickMultiplier = useGameStore((s) => s.state.clickMultiplier);
}; const productionPerSecond = useGameStore((s) => s.productionPerSecond);
const playSeconds = useGameStore((s) => s.playSeconds);
const { if (isVisible) return null;
manic,
snowman,
bonnet,
sugar,
cookie,
couronne,
epice,
biere,
coffee,
} = useWildCoin();
const { incrementClick, incrementPerSecond } = useWildCoin();
const hiddenDiv = isVisible ? "none" : null;
return ( return (
<div className="hudContainer"> <div className="fixed top-20 left-1/2 -translate-x-1/2 z-10 flex flex-col items-center gap-2 px-6 py-3 rounded-xl bg-gray-900/90 backdrop-blur-sm text-white font-[var(--font)]">
<div style={{ display: hiddenDiv }} className="hudStats"> <div className="flex gap-6 text-sm">
<div className="time section"> <div className="flex flex-col items-center">
<p>Temps de jeu</p> <span className="text-gray-400 text-xs">Temps</span>
<p><Timer /></p> <span>{formatTime(playSeconds)}</span>
</div> </div>
<div className="auto section"> <div className="flex flex-col items-center">
<p>Auto CPS</p> <span className="text-gray-400 text-xs">Têtards/s</span>
<p>{incrementPerSecond}</p> <span>{formatNumber(productionPerSecond)}</span>
</div> </div>
<div className="player section"> <div className="flex flex-col items-center">
<p>Player Click</p> <span className="text-gray-400 text-xs">Ponte</span>
<p>{incrementClick}</p> <span>{clickMultiplier}</span>
</div> </div>
</div> </div>
<div className="hudBooster"> <div className="text-lg font-bold text-emerald-400">
{coffee[0] === true ? ( {formatNumber(resources)}
<div className="boosterItem"> </div>
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Tasse.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{coffee[1]}</p>
</div>
</div>
) : null}
{manic[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Hand.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{manic[1]}</p>
</div>
</div>
) : null}
{snowman[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Bonhome.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{snowman[1]}</p>
</div>
</div>
) : null}
{bonnet[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Bonnet.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{bonnet[1]}</p>
</div>
</div>
) : null}
{sugar[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Canne.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{sugar[1]}</p>
</div>
</div>
) : null}
{cookie[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Cookie.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{cookie[1]}</p>
</div>
</div>
) : null}
{couronne[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Courone.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{couronne[1]}</p>
</div>
</div>
) : null}
{epice[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/PainDep.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{epice[1]}</p>
</div>
</div>
) : null}
{biere[0] === true ? (
<div className="boosterItem">
<div
className="boosterIcon"
style={{ backgroundImage: `url(/svg/Beer.svg)` }}
alt="coffee"
/>
<div className="countbox">
<p className="boosterCount">{biere[1]}</p>
</div>
</div>
) : null}
</div>
</div> </div>
); );
} }

View File

@@ -1,50 +1,37 @@
// MilestoneBar.tsx — Progression vers le prochain prestige // MilestoneBar.tsx — Progression vers le prochain prestige
// Barre visuelle ressources / 1 000 000 + indicateur restant // Barre visuelle ressources / 1 000 000
import React from "react"; import { useGameStore } from "../store/useGameStore";
import { formatNumber } from "../utils/formatNumber";
const PRESTIGE_THRESHOLD = 1_000_000; const PRESTIGE_THRESHOLD = 1_000_000;
interface MilestoneBarProps { export function MilestoneBar() {
resources: number; const resources = useGameStore((s) => s.state.resources);
}
export function MilestoneBar({ resources }: MilestoneBarProps) {
const progress = Math.min(resources / PRESTIGE_THRESHOLD, 1); const progress = Math.min(resources / PRESTIGE_THRESHOLD, 1);
const progressPercent = (progress * 100).toFixed(1); const progressPercent = (progress * 100).toFixed(1);
const remaining = Math.max(PRESTIGE_THRESHOLD - resources, 0); const remaining = Math.max(PRESTIGE_THRESHOLD - resources, 0);
const formatNumber = (n: number): string => {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
return Math.floor(n).toString();
};
return ( return (
<div className="milestone-bar" aria-label="Progression vers le prestige"> <div className="flex flex-col gap-1 max-w-md w-full">
<div className="milestone-label"> <div className="text-xs text-gray-300 flex justify-between">
Prochain prestige : {formatNumber(resources)} / {formatNumber(PRESTIGE_THRESHOLD)} <span>Prochaine Génération</span>
<span>
{formatNumber(resources)} / {formatNumber(PRESTIGE_THRESHOLD)}
</span>
</div> </div>
<div <div className="h-2 bg-gray-800 rounded-full overflow-hidden">
className="milestone-track"
role="progressbar"
aria-valuenow={Math.floor(progress * 100)}
aria-valuemin={0}
aria-valuemax={100}
>
<div <div
className="milestone-fill" className="h-full bg-gradient-to-r from-purple-600 to-purple-400 transition-all duration-500 rounded-full"
style={{ width: `${progressPercent}%` }} style={{ width: `${progressPercent}%` }}
/> />
</div> </div>
{remaining > 0 && ( <div className="text-xs text-gray-400 text-right">
<div className="milestone-remaining"> {remaining > 0
{formatNumber(remaining)} ressources restantes ? `${formatNumber(remaining)} têtards restants`
</div> : "Nouvelle Génération disponible !"}
)} </div>
{remaining === 0 && (
<div className="milestone-ready">Prestige disponible !</div>
)}
</div> </div>
); );
} }

View File

@@ -1,54 +1,50 @@
// PrestigePanel.tsx — Boucle de prestige long terme // PrestigePanel.tsx — Nouvelle Génération (prestige)
// Visible uniquement quand canPrestige = true (ressources 1 000 000) // Visible uniquement quand canPrestige = true (ressources >= 1 000 000)
import React from "react"; import { useGameStore } from "../store/useGameStore";
import { computePrestigeDna } from "../core/economy";
interface PrestigePanelProps { export function PrestigePanel() {
prestigeCount: number; const { prestigeCount, prestigeMultiplier, ancestralDna, lifetimeTadpoles } =
prestigeMultiplier: number; useGameStore((s) => s.state);
canPrestige: boolean; const canPrestige = useGameStore((s) => s.canPrestige);
onPrestige: () => void; const prestige = useGameStore((s) => s.prestige);
}
const dnaPreview = computePrestigeDna(lifetimeTadpoles);
export function PrestigePanel({
prestigeCount,
prestigeMultiplier,
canPrestige,
onPrestige,
}: PrestigePanelProps) {
const handlePrestige = () => { const handlePrestige = () => {
const confirmed = window.confirm( const confirmed = window.confirm(
`Prestige — Reset total : ressources et générateurs à zéro.\n` + `Nouvelle Génération\n\n` +
`Récompense : +0.1× multiplicateur permanent.\n\n` + `Reset : têtards et générateurs à zéro.\n` +
`Multiplicateur actuel : ×${prestigeMultiplier.toFixed(1)}\n` + `Récompense : +${dnaPreview} ADN Ancestral\n` +
`Multiplicateur après : ×${(prestigeMultiplier + 0.1).toFixed(1)}\n\n` + ` +0.1x multiplicateur permanent\n\n` +
`Confirmer le prestige ?` `ADN actuel : ${ancestralDna}\n` +
`ADN après : ${ancestralDna + dnaPreview}\n` +
`Multiplicateur : x${prestigeMultiplier.toFixed(1)} → x${(prestigeMultiplier + 0.1).toFixed(1)}\n\n` +
`L'Arbre d'Évolution persiste.\n\n` +
`Confirmer la Nouvelle Génération ?`
); );
if (confirmed) { if (confirmed) prestige();
onPrestige();
}
}; };
return ( return (
<div className="prestige-panel"> <div className="flex flex-col gap-2 p-4 rounded-xl bg-purple-900/60 backdrop-blur-sm max-w-md w-full">
<div className="prestige-stats"> <div className="flex flex-wrap gap-4 text-sm text-purple-200">
<span className="prestige-count">Prestiges : {prestigeCount}</span> <span>Générations : {prestigeCount}</span>
<span className="prestige-multiplier"> <span>Mult : x{prestigeMultiplier.toFixed(1)}</span>
Multiplicateur : ×{prestigeMultiplier.toFixed(1)} <span>ADN : {ancestralDna}</span>
</span>
</div> </div>
{canPrestige && ( {canPrestige && (
<div className="prestige-action"> <div className="flex flex-col gap-2 mt-2">
<div className="prestige-reward"> <p className="text-sm text-purple-100">
Récompense disponible : <strong>+0.1× multiplicateur permanent</strong> Nouvelle Génération : <strong>+{dnaPreview} ADN</strong> + <strong>+0.1x mult</strong>
</div> </p>
<button <button
className="prestige-button"
onClick={handlePrestige} onClick={handlePrestige}
aria-label="Déclencher le prestige" className="px-4 py-2 rounded-lg bg-purple-600 hover:bg-purple-500 text-white font-semibold text-sm transition-colors cursor-pointer"
> >
Prestige Nouvelle Génération
</button> </button>
</div> </div>
)} )}

View File

@@ -1,75 +0,0 @@
import { useWildCoin } from "./WildCoinContext";
function Ameliorations() {
const {
wildCoin,
incrementClick,
setWildCoin,
setIncrementClick,
incrementPerSecond,
setIncrementPerSecond,
} = useWildCoin();
const activePrices = [5, 15, 50, 500]; // prix
const passivePrices = [5, 15, 50, 500];
const activeIncrementValues = [1, 3, 10, 100]; // boost = incrementValue
const passiveIncrementValues = [1, 3, 10, 100]; // = incrementValue
const acheterAmelioration = (type, amount) => {
const prices = type === "actif" ? activePrices : passivePrices;
const incrementValues =
type === "actif" ? activeIncrementValues : passiveIncrementValues;
const price = prices[amount - 1];
const incrementValue = incrementValues[amount - 1];
if (wildCoin >= price) {
if (type === "actif") {
setIncrementClick(incrementClick + incrementValue);
} else if (type === "passif") {
setIncrementPerSecond(incrementPerSecond + incrementValue);
}
setWildCoin(wildCoin - price);
} else {
console.log("Pas assez de WildCoin pour acheter cette amélioration.");
}
};
return (
<div className="divMagasinAmelio">
<h2>Magasin d'Améliorations</h2>
<div className="divAmelioActives">
<p>Améliorations Actives :</p>
{[1, 2, 3, 4].map((amount) => (
<div key={amount}>
Price: {activePrices[amount - 1]} - (+
{activeIncrementValues[amount - 1]})
<button
className="amelioActives"
onClick={() => acheterAmelioration("actif", amount)}
>
Acheter
</button>
</div>
))}
</div>
<div className="divAmelioPassives">
<p>Améliorations Passives :</p>
{[1, 2, 3, 4].map((amount) => (
<div key={amount}>
Price: {passivePrices[amount - 1]} - (+
{passiveIncrementValues[amount - 1]})
<button
className="amelioPassives"
onClick={() => acheterAmelioration("passif", amount)}
>
Acheter
</button>
</div>
))}
</div>
</div>
);
}
export default Ameliorations;

View File

@@ -1,43 +0,0 @@
import { createContext, useContext, useState, useEffect } from "react";
export const WildCoinContext = createContext();
export const useWildCoin = () => {
return useContext(WildCoinContext);
};
export function WildCoinProvider({ children }) {
// Value of coin
const [wildCoin, setWildCoin] = useState(0);
// increment by click state
const [incrementClick, setIncrementClick] = useState(1);
// increment inner useEffect state
const [incrementPerSecond, setIncrementPerSecond] = useState(1);
const incrementWildCoin = (amount) => {
setWildCoin((prevWildCoin) => prevWildCoin + amount);
};
/**
* @passiveGenerationInterval incre per sec wild coin in wildCoin
* */
useEffect(() => {
const passiveGenerationInterval = setInterval(() => {
incrementWildCoin(incrementPerSecond);
}, 1000);
return () => clearInterval(passiveGenerationInterval);
}, [incrementPerSecond]);
const value = {
wildCoin,
setWildCoin,
incrementClick,
incrementWildCoin,
};
return (
<WildCoinContext.Provider value={value}>
{children}
</WildCoinContext.Provider>
);
}

View File

@@ -1,138 +0,0 @@
import { createContext, useContext, useState, useEffect } from "react";
export const WildCoinContext = createContext();
export const useWildCoin = () => {
return useContext(WildCoinContext);
};
export function WildCoinProvider({ children }) {
const initialState = {
wildCoin: 0,
incrementClick: 1,
incrementPerSecond: 0,
};
const [state, setState] = useState(() => {
const storedContext = JSON.parse(localStorage.getItem("wildCoinContext"));
return {
...initialState,
...(storedContext || {}),
};
});
const [coffee, setCoffee] = useState([false, 0]);
const [manic, setManic] = useState([false, 0]);
const [snowman, setSnowman] = useState([false, 0]);
const [bonnet, setBonnet] = useState([false, 0]);
const [sugar, setSugar] = useState([false, 0]);
const [cookie, setCookie] = useState([false, 0]);
const [couronne, setCouronne] = useState([false, 0]);
const [epice, setEpice] = useState([false, 0]);
const [biere, setBiere] = useState([false, 0]);
const [santaDrunk, setSantaDrunk] = useState(false);
const updateWildCoin = (amount) => {
setState((prev) => ({
...prev,
wildCoin: prev.wildCoin + amount,
}));
};
const incrementWildCoin = (amount) => {
updateWildCoin(amount);
};
const setIncrementClick = (amount) => {
setState((prev) => ({
...prev,
incrementClick: amount,
}));
};
const setIncrementPerSecond = (amount) => {
setState((prev) => ({
...prev,
incrementPerSecond: amount,
}));
};
const setWildCoin = (amount) => {
setState((prev) => ({
...prev,
wildCoin: amount,
}));
};
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setSeconds((prevSeconds) => prevSeconds + 1);
}, 1000);
return () => clearInterval(intervalId);
}, []);
const formatTime = (time) => {
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
const formattedTime = `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
return formattedTime;
};
useEffect(() => {
localStorage.setItem("wildCoinContext", JSON.stringify(state));
}, [state]);
useEffect(() => {
const passiveGenerationInterval = setInterval(() => {
updateWildCoin(state.incrementPerSecond);
}, 1000);
return () => clearInterval(passiveGenerationInterval);
}, [state.incrementPerSecond]);
const contextValue = {
...state,
incrementWildCoin,
setIncrementClick,
setIncrementPerSecond,
setWildCoin,
coffee,
setCoffee,
manic,
setManic,
snowman,
setSnowman,
bonnet,
setBonnet,
sugar,
setSugar,
cookie,
setCookie,
couronne,
setCouronne,
epice,
setEpice,
biere,
setBiere,
setSantaDrunk,
santaDrunk,
seconds,
setSeconds,
formatTime,
};
return (
<WildCoinContext.Provider value={contextValue}>
{children}
</WildCoinContext.Provider>
);
}

View File

@@ -1,16 +0,0 @@
import { useWildCoin } from "./WildCoinContext";
import WildCoinS from "../../../public/WildCoin.svg";
function WildCoinIncrementAction() {
const { incrementClick, incrementWildCoin } = useWildCoin();
const handleIncrement = () => {
incrementWildCoin(incrementClick);
};
return (
<img src={WildCoinS} className="wildCoinBtn" style={{width:"40px", height:"40px"}} alt="Clique pour augmenter le score" aria-label="Clique pour augmenter le score" onClick={handleIncrement} />
);
}
export default WildCoinIncrementAction;

View File

@@ -1,13 +0,0 @@
import { useWildCoin } from "../WildCoin/WildCoinContext";
function Timer() {
const { formatTime, seconds } = useWildCoin();
return (
<div>
<p>{formatTime(seconds)}</p>
</div>
);
}
export default Timer;

View File

@@ -1,93 +0,0 @@
// useEconomy.ts — Hook React avec lazy calculation + localStorage
// Pas de setInterval pour les gains passifs — tout est calculé au read
import { useState, useCallback, useEffect } from "react";
import {
GameState,
DEFAULT_STATE,
applyIdleGains,
applyClick,
buyGenerator,
applyPrestige,
canPrestige,
totalProductionPerSecond,
generatorCost,
} from "../core/economy";
const SAVE_KEY = "clickerz_state";
function loadState(): GameState {
try {
const raw = localStorage.getItem(SAVE_KEY);
if (!raw) return { ...DEFAULT_STATE, lastTick: Date.now() };
const saved = JSON.parse(raw) as GameState;
// Appliquer les gains idle accumulés pendant l'absence
return applyIdleGains(saved, Date.now());
} catch {
return { ...DEFAULT_STATE, lastTick: Date.now() };
}
}
function saveState(state: GameState): void {
localStorage.setItem(SAVE_KEY, JSON.stringify(state));
}
export function useEconomy() {
const [state, setState] = useState<GameState>(loadState);
// Auto-save + tick UI toutes les secondes (pour rafraîchir l'affichage uniquement)
// La vraie valeur est calculée lazily dans totalProductionPerSecond
useEffect(() => {
const id = setInterval(() => {
setState((prev) => {
const updated = applyIdleGains(prev, Date.now());
saveState(updated);
return updated;
});
}, 1000);
return () => clearInterval(id);
}, []);
const click = useCallback(() => {
setState((prev) => {
const updated = applyClick(applyIdleGains(prev, Date.now()));
saveState(updated);
return updated;
});
}, []);
const buy = useCallback((genId: string) => {
setState((prev) => {
const withIdle = applyIdleGains(prev, Date.now());
const updated = buyGenerator(withIdle, genId);
if (!updated) return prev;
saveState(updated);
return updated;
});
}, []);
const prestige = useCallback(() => {
setState((prev) => {
if (!canPrestige(prev)) return prev;
const updated = applyPrestige(prev);
saveState(updated);
return updated;
});
}, []);
const reset = useCallback(() => {
const fresh = { ...DEFAULT_STATE, lastTick: Date.now() };
saveState(fresh);
setState(fresh);
}, []);
return {
state,
click,
buy,
prestige,
canPrestige: canPrestige(state),
productionPerSecond: totalProductionPerSecond(state),
generatorCost,
};
}

View File

@@ -1,7 +1,9 @@
@import "tailwindcss";
:root { :root {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 1px; width: 1px;

View File

@@ -1,14 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import AchievementsCard from "../components/AchievementsCard"; import AchievementsCard from "../components/AchievementsCard";
import "../scss/achievements.scss"; import "../scss/achievements.scss";
import { useWildCoin } from "../components/WildCoin/WildCoinContext"; import { useGameStore } from "../store/useGameStore";
import achievements from "../data/Achievements.json"; import achievements from "../data/Achievements.json";
function Achievements() { function Achievements() {
const { wildCoin } = useWildCoin(); const resources = useGameStore((s) => s.state.resources);
let score = 1; let score = 1;
if (wildCoin >= 25) { if (resources >= 25) {
score = Math.floor((wildCoin - 25) / 400) + 1; score = Math.floor((resources - 25) / 400) + 1;
} else { } else {
score = 0; score = 0;
} }

View File

@@ -1,102 +0,0 @@
.hudContainer {
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-items: center;
justify-content: space-around;
min-width: 260px;
width: fit-content;
max-width: 1280px;
height: fit-content;
gap: 1rem;
padding: 1rem;
border-radius: 8px;
box-sizing: border-box;
background-color: var(--color-grey);
color: var(--color-white);
font-family: var(--font);
color: aliceblue;
font-size: 1rem;
text-align: center;
position: absolute;
left: 50%;
transform: translate(-50%);
z-index: 2;
.hudStats {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
.section {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
}
.hudBooster {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
padding: 1rem;
gap: 1rem;
min-width: 280px;
width: auto;
max-width: 1280px;
height: fit-content;
color: var(--color-white);
font-family: var(--font);
color: aliceblue;
font-size: 1rem;
text-align: center;
border-radius: 8px;
box-sizing: border-box;
.boosterItem {
display: flex;
flex-wrap: wrap;
width: 30px;
height: 30px;
gap: 0.6rem;
.boosterIcon {
width: 100%;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.countbox {
position: absolute;
margin-top: 20px;
margin-left: 15px;
border: solid 1px var(--color-white);
background-color: var(--color-white);
border-radius: 20%;
padding: 0.1rem;
color: var(--color-grey);
min-width: 20px;
width: fit-content;
height: 20px;
box-shadow: -1px -1px 7px 0px var(--color-grey);
}
.boosterCount {
font-family: var(--font);
font-size: 0.7rem;
text-align: center;
font-weight: 900;
}
}
}
}

View File

@@ -0,0 +1,146 @@
// useGameStore.ts — Zustand store, source unique de l'état game
// Lazy calculation pattern : gains passifs calculés au read depuis lastTick
import { create } from "zustand";
import {
GameState,
DEFAULT_STATE,
applyIdleGains,
applyClick,
buyGenerator,
buyEvolutionNode,
applyPrestige,
canPrestige as canPrestigeCheck,
totalProductionPerSecond,
generatorCost as genCost,
} from "../core/economy";
const SAVE_KEY = "clickerz_state";
function loadState(): GameState {
try {
const raw = localStorage.getItem(SAVE_KEY);
if (!raw) return { ...DEFAULT_STATE, lastTick: Date.now() };
const saved = JSON.parse(raw) as GameState;
return applyIdleGains(saved, Date.now());
} catch {
return { ...DEFAULT_STATE, lastTick: Date.now() };
}
}
function saveState(state: GameState): void {
localStorage.setItem(SAVE_KEY, JSON.stringify(state));
}
interface GameStore {
// State
state: GameState;
playSeconds: number;
// Derived (recalculated on tick)
canPrestige: boolean;
productionPerSecond: number;
// Actions
tick: () => void;
click: () => void;
buy: (genId: string) => void;
buyNode: (nodeId: string) => void;
prestige: () => void;
reset: () => void;
loadFromServer: (serverState: GameState) => void;
generatorCost: typeof genCost;
}
export const useGameStore = create<GameStore>((set, get) => ({
state: loadState(),
playSeconds: 0,
canPrestige: canPrestigeCheck(loadState()),
productionPerSecond: totalProductionPerSecond(loadState()),
tick: () => {
set((s) => {
const updated = applyIdleGains(s.state, Date.now());
saveState(updated);
return {
state: updated,
playSeconds: s.playSeconds + 1,
canPrestige: canPrestigeCheck(updated),
productionPerSecond: totalProductionPerSecond(updated),
};
});
},
click: () => {
set((s) => {
const updated = applyClick(applyIdleGains(s.state, Date.now()));
saveState(updated);
return {
state: updated,
canPrestige: canPrestigeCheck(updated),
productionPerSecond: totalProductionPerSecond(updated),
};
});
},
buy: (genId: string) => {
set((s) => {
const withIdle = applyIdleGains(s.state, Date.now());
const updated = buyGenerator(withIdle, genId);
if (!updated) return s;
saveState(updated);
return {
state: updated,
productionPerSecond: totalProductionPerSecond(updated),
};
});
},
buyNode: (nodeId: string) => {
set((s) => {
const updated = buyEvolutionNode(s.state, nodeId);
if (!updated) return s;
saveState(updated);
return {
state: updated,
productionPerSecond: totalProductionPerSecond(updated),
};
});
},
prestige: () => {
set((s) => {
if (!canPrestigeCheck(s.state)) return s;
const updated = applyPrestige(s.state);
saveState(updated);
return {
state: updated,
canPrestige: canPrestigeCheck(updated),
productionPerSecond: totalProductionPerSecond(updated),
};
});
},
reset: () => {
const fresh = { ...DEFAULT_STATE, lastTick: Date.now() };
saveState(fresh);
set({
state: fresh,
playSeconds: 0,
canPrestige: false,
productionPerSecond: 0,
});
},
loadFromServer: (serverState: GameState) => {
const hydrated = applyIdleGains(serverState, Date.now());
saveState(hydrated);
set({
state: hydrated,
canPrestige: canPrestigeCheck(hydrated),
productionPerSecond: totalProductionPerSecond(hydrated),
});
},
generatorCost: genCost,
}));

View File

@@ -1,8 +1,9 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [tailwindcss(), react()],
test: { test: {
environment: 'node', environment: 'node',
}, },