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

@@ -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,
}));