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:
456
Frontend/package-lock.json
generated
456
Frontend/package-lock.json
generated
@@ -8,12 +8,15 @@
|
||||
"name": "template",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-lottie-player": "^1.5.5",
|
||||
"react-router-dom": "^6.19.0",
|
||||
"sass": "^1.69.5"
|
||||
"sass": "^1.69.5",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"zustand": "^5.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.28",
|
||||
@@ -381,7 +384,6 @@
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -393,7 +395,6 @@
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz",
|
||||
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -404,7 +405,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
|
||||
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -418,7 +418,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
@@ -434,7 +433,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -450,7 +448,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -466,7 +463,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -482,7 +478,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -498,7 +493,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -514,7 +508,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@@ -530,7 +523,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
@@ -546,7 +538,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -562,7 +553,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -578,7 +568,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -594,7 +583,6 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -610,7 +598,6 @@
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -626,7 +613,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -642,7 +628,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -658,7 +643,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -674,7 +658,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -708,7 +691,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
@@ -742,7 +724,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
@@ -776,7 +757,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
@@ -792,7 +772,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -808,7 +787,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -824,7 +802,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -938,33 +915,29 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"dev": true,
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||
"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": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
@@ -973,14 +946,13 @@
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||
"dev": true,
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
@@ -990,7 +962,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
|
||||
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -1631,7 +1602,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -1644,7 +1614,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
@@ -1657,7 +1626,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -1670,7 +1638,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -1683,7 +1650,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1696,7 +1662,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1709,7 +1674,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1722,7 +1686,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1735,7 +1698,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1748,7 +1710,6 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1761,7 +1722,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1774,7 +1734,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1787,7 +1746,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -1800,7 +1758,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -1813,7 +1770,6 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -1826,7 +1782,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -1839,11 +1794,267 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -1912,20 +2123,19 @@
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
|
||||
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -2424,7 +2634,7 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
@@ -2485,7 +2695,6 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -2509,6 +2718,19 @@
|
||||
"integrity": "sha512-hohItzsQcG7/FBsviCYMtQwUSWvVF7NVqPOnJCErWsAshsP/CR2LAXdmq276RbESNdhxiAq5/vRo1g2pxGXVww==",
|
||||
"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": {
|
||||
"version": "1.22.3",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz",
|
||||
@@ -2635,7 +2857,6 @@
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
@@ -3120,7 +3341,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3274,6 +3494,12 @@
|
||||
"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": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
|
||||
@@ -3761,6 +3987,15 @@
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -3861,7 +4096,6 @@
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
@@ -3894,7 +4128,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3915,7 +4148,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3936,7 +4168,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3957,7 +4188,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3978,7 +4208,6 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3999,7 +4228,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4020,7 +4248,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4041,7 +4268,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4062,7 +4288,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4083,7 +4308,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4104,7 +4328,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4168,7 +4391,6 @@
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -4196,7 +4418,6 @@
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -4458,7 +4679,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
@@ -4478,7 +4698,6 @@
|
||||
"version": "8.5.8",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
||||
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -4805,7 +5024,6 @@
|
||||
"version": "4.17.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
|
||||
"integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
},
|
||||
@@ -5135,6 +5353,25 @@
|
||||
"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": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
@@ -5198,7 +5435,6 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
@@ -5363,7 +5599,6 @@
|
||||
"version": "5.2.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
|
||||
"integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.20.1",
|
||||
"postcss": "^8.4.38",
|
||||
@@ -6191,6 +6426,35 @@
|
||||
"funding": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-lottie-player": "^1.5.5",
|
||||
"react-router-dom": "^6.19.0",
|
||||
"sass": "^1.69.5"
|
||||
"sass": "^1.69.5",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"zustand": "^5.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.28",
|
||||
|
||||
@@ -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/buttons.scss";
|
||||
import PropTypes from "prop-types";
|
||||
import { useGameStore } from "../store/useGameStore";
|
||||
|
||||
export default function BoutiqueCard({
|
||||
name,
|
||||
@@ -9,89 +11,13 @@ export default function BoutiqueCard({
|
||||
incrementValue,
|
||||
description,
|
||||
image,
|
||||
link,
|
||||
type,
|
||||
buyed,
|
||||
}) {
|
||||
BoutiqueCard.propTypes = {
|
||||
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 resources = useGameStore((s) => s.state.resources);
|
||||
|
||||
const {
|
||||
wildCoin,
|
||||
incrementClick,
|
||||
setWildCoin,
|
||||
setIncrementClick,
|
||||
incrementPerSecond,
|
||||
setIncrementPerSecond,
|
||||
setCoffee,
|
||||
setSantaDrunk,
|
||||
setManic,
|
||||
setSnowman,
|
||||
setBonnet,
|
||||
setSugar,
|
||||
setCookie,
|
||||
setCouronne,
|
||||
setEpice,
|
||||
setBiere,
|
||||
} = useWildCoin();
|
||||
// Legacy shop — disabled for now, generators are in GeneratorShop
|
||||
const canAfford = resources >= price;
|
||||
|
||||
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 (
|
||||
<div className="shopcardcontainer">
|
||||
<div className="shopcontainer">
|
||||
@@ -105,7 +31,6 @@ export default function BoutiqueCard({
|
||||
<p className="itemname">{name}</p>
|
||||
<div className="price">
|
||||
<p className="itemprice">{price}</p>
|
||||
|
||||
<div className="priceicon" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,12 +44,22 @@ export default function BoutiqueCard({
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => acheterAmelioration(type, price, name)}
|
||||
disabled={!canAfford}
|
||||
className="primary-button"
|
||||
style={{ opacity: canAfford ? 1 : 0.5 }}
|
||||
>
|
||||
Acheter
|
||||
Bientôt
|
||||
</button>
|
||||
</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,
|
||||
};
|
||||
|
||||
99
Frontend/src/components/EvolutionTree.tsx
Normal file
99
Frontend/src/components/EvolutionTree.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
16
Frontend/src/components/GameTick.tsx
Normal file
16
Frontend/src/components/GameTick.tsx
Normal 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;
|
||||
}
|
||||
51
Frontend/src/components/GeneratorShop.tsx
Normal file
51
Frontend/src/components/GeneratorShop.tsx
Normal 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 · 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>
|
||||
);
|
||||
}
|
||||
@@ -1,154 +1,40 @@
|
||||
import "../../scss/components/Hud.scss";
|
||||
import { useWildCoin } from "../WildCoin/WildCoinContext";
|
||||
import Timer from "../timer/Timer";
|
||||
import propTypes from "prop-types";
|
||||
// Hud.jsx — Stats HUD (Zustand)
|
||||
import { useGameStore } from "../../store/useGameStore";
|
||||
import { formatNumber } from "../../utils/formatNumber";
|
||||
|
||||
function Hud({ isVisible }) {
|
||||
Hud.propTypes = {
|
||||
isVisible: propTypes.bool.isRequired,
|
||||
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")}`;
|
||||
};
|
||||
|
||||
const {
|
||||
manic,
|
||||
snowman,
|
||||
bonnet,
|
||||
sugar,
|
||||
cookie,
|
||||
couronne,
|
||||
epice,
|
||||
biere,
|
||||
coffee,
|
||||
} = useWildCoin();
|
||||
function Hud({ isVisible }) {
|
||||
const resources = useGameStore((s) => s.state.resources);
|
||||
const clickMultiplier = useGameStore((s) => s.state.clickMultiplier);
|
||||
const productionPerSecond = useGameStore((s) => s.productionPerSecond);
|
||||
const playSeconds = useGameStore((s) => s.playSeconds);
|
||||
|
||||
const { incrementClick, incrementPerSecond } = useWildCoin();
|
||||
const hiddenDiv = isVisible ? "none" : null;
|
||||
if (isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="hudContainer">
|
||||
<div style={{ display: hiddenDiv }} className="hudStats">
|
||||
<div className="time section">
|
||||
<p>Temps de jeu</p>
|
||||
<p><Timer /></p>
|
||||
<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 className="flex gap-6 text-sm">
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-gray-400 text-xs">Temps</span>
|
||||
<span>{formatTime(playSeconds)}</span>
|
||||
</div>
|
||||
<div className="auto section">
|
||||
<p>Auto CPS</p>
|
||||
<p>{incrementPerSecond}</p>
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-gray-400 text-xs">Têtards/s</span>
|
||||
<span>{formatNumber(productionPerSecond)}</span>
|
||||
</div>
|
||||
<div className="player section">
|
||||
<p>Player Click</p>
|
||||
<p>{incrementClick}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="hudBooster">
|
||||
{coffee[0] === true ? (
|
||||
<div className="boosterItem">
|
||||
<div
|
||||
className="boosterIcon"
|
||||
style={{ backgroundImage: `url(/svg/Tasse.svg)` }}
|
||||
alt="coffee"
|
||||
/>
|
||||
<div className="countbox">
|
||||
<p className="boosterCount">{coffee[1]}</p>
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="text-gray-400 text-xs">Ponte</span>
|
||||
<span>{clickMultiplier}</span>
|
||||
</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 className="text-lg font-bold text-emerald-400">
|
||||
{formatNumber(resources)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,50 +1,37 @@
|
||||
// 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;
|
||||
|
||||
interface MilestoneBarProps {
|
||||
resources: number;
|
||||
}
|
||||
export function MilestoneBar() {
|
||||
const resources = useGameStore((s) => s.state.resources);
|
||||
|
||||
export function MilestoneBar({ resources }: MilestoneBarProps) {
|
||||
const progress = Math.min(resources / PRESTIGE_THRESHOLD, 1);
|
||||
const progressPercent = (progress * 100).toFixed(1);
|
||||
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 (
|
||||
<div className="milestone-bar" aria-label="Progression vers le prestige">
|
||||
<div className="milestone-label">
|
||||
Prochain prestige : {formatNumber(resources)} / {formatNumber(PRESTIGE_THRESHOLD)}
|
||||
<div className="flex flex-col gap-1 max-w-md w-full">
|
||||
<div className="text-xs text-gray-300 flex justify-between">
|
||||
<span>Prochaine Génération</span>
|
||||
<span>
|
||||
{formatNumber(resources)} / {formatNumber(PRESTIGE_THRESHOLD)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="milestone-track"
|
||||
role="progressbar"
|
||||
aria-valuenow={Math.floor(progress * 100)}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
>
|
||||
<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}%` }}
|
||||
/>
|
||||
</div>
|
||||
{remaining > 0 && (
|
||||
<div className="milestone-remaining">
|
||||
{formatNumber(remaining)} ressources restantes
|
||||
<div className="text-xs text-gray-400 text-right">
|
||||
{remaining > 0
|
||||
? `${formatNumber(remaining)} têtards restants`
|
||||
: "Nouvelle Génération disponible !"}
|
||||
</div>
|
||||
)}
|
||||
{remaining === 0 && (
|
||||
<div className="milestone-ready">Prestige disponible !</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,54 +1,50 @@
|
||||
// PrestigePanel.tsx — Boucle de prestige long terme
|
||||
// Visible uniquement quand canPrestige = true (ressources ≥ 1 000 000)
|
||||
// PrestigePanel.tsx — Nouvelle Génération (prestige)
|
||||
// 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 {
|
||||
prestigeCount: number;
|
||||
prestigeMultiplier: number;
|
||||
canPrestige: boolean;
|
||||
onPrestige: () => void;
|
||||
}
|
||||
export function PrestigePanel() {
|
||||
const { prestigeCount, prestigeMultiplier, ancestralDna, lifetimeTadpoles } =
|
||||
useGameStore((s) => s.state);
|
||||
const canPrestige = useGameStore((s) => s.canPrestige);
|
||||
const prestige = useGameStore((s) => s.prestige);
|
||||
|
||||
const dnaPreview = computePrestigeDna(lifetimeTadpoles);
|
||||
|
||||
export function PrestigePanel({
|
||||
prestigeCount,
|
||||
prestigeMultiplier,
|
||||
canPrestige,
|
||||
onPrestige,
|
||||
}: PrestigePanelProps) {
|
||||
const handlePrestige = () => {
|
||||
const confirmed = window.confirm(
|
||||
`Prestige — Reset total : ressources et générateurs à zéro.\n` +
|
||||
`Récompense : +0.1× multiplicateur permanent.\n\n` +
|
||||
`Multiplicateur actuel : ×${prestigeMultiplier.toFixed(1)}\n` +
|
||||
`Multiplicateur après : ×${(prestigeMultiplier + 0.1).toFixed(1)}\n\n` +
|
||||
`Confirmer le prestige ?`
|
||||
`Nouvelle Génération\n\n` +
|
||||
`Reset : têtards et générateurs à zéro.\n` +
|
||||
`Récompense : +${dnaPreview} ADN Ancestral\n` +
|
||||
` +0.1x multiplicateur permanent\n\n` +
|
||||
`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) {
|
||||
onPrestige();
|
||||
}
|
||||
if (confirmed) prestige();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="prestige-panel">
|
||||
<div className="prestige-stats">
|
||||
<span className="prestige-count">Prestiges : {prestigeCount}</span>
|
||||
<span className="prestige-multiplier">
|
||||
Multiplicateur : ×{prestigeMultiplier.toFixed(1)}
|
||||
</span>
|
||||
<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="flex flex-wrap gap-4 text-sm text-purple-200">
|
||||
<span>Générations : {prestigeCount}</span>
|
||||
<span>Mult : x{prestigeMultiplier.toFixed(1)}</span>
|
||||
<span>ADN : {ancestralDna}</span>
|
||||
</div>
|
||||
|
||||
{canPrestige && (
|
||||
<div className="prestige-action">
|
||||
<div className="prestige-reward">
|
||||
Récompense disponible : <strong>+0.1× multiplicateur permanent</strong>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
<p className="text-sm text-purple-100">
|
||||
Nouvelle Génération : <strong>+{dnaPreview} ADN</strong> + <strong>+0.1x mult</strong>
|
||||
</p>
|
||||
<button
|
||||
className="prestige-button"
|
||||
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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useState } from "react";
|
||||
import AchievementsCard from "../components/AchievementsCard";
|
||||
import "../scss/achievements.scss";
|
||||
import { useWildCoin } from "../components/WildCoin/WildCoinContext";
|
||||
import { useGameStore } from "../store/useGameStore";
|
||||
import achievements from "../data/Achievements.json";
|
||||
|
||||
function Achievements() {
|
||||
const { wildCoin } = useWildCoin();
|
||||
const resources = useGameStore((s) => s.state.resources);
|
||||
let score = 1;
|
||||
if (wildCoin >= 25) {
|
||||
score = Math.floor((wildCoin - 25) / 400) + 1;
|
||||
if (resources >= 25) {
|
||||
score = Math.floor((resources - 25) / 400) + 1;
|
||||
} else {
|
||||
score = 0;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Frontend/src/store/useGameStore.ts
Normal file
146
Frontend/src/store/useGameStore.ts
Normal 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,
|
||||
}));
|
||||
@@ -1,8 +1,9 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [tailwindcss(), react()],
|
||||
test: {
|
||||
environment: 'node',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user