From a665fdf2f442d7b66923d72cda515c4957acd089 Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Sat, 28 Mar 2026 18:47:41 +0100 Subject: [PATCH] feat: toast notifications + guide du gardien - Toast system: store Zustand + ToastContainer (slide-in, auto-dismiss) - Toasts on: prestige, milestone claim, capstone unlock, cosmetic unlock - Guide in-game: /guide route, toutes les mecaniques expliquees - Lien navbar + sidebar --- Frontend/src/App.jsx | 2 + Frontend/src/components/ToastContainer.tsx | 56 +++++++++++ Frontend/src/data/NavBarData.json | 6 ++ Frontend/src/index.css | 11 +++ Frontend/src/main.jsx | 5 + Frontend/src/pages/Guide.tsx | 105 +++++++++++++++++++++ Frontend/src/pages/Home.jsx | 5 +- Frontend/src/store/useGameStore.ts | 8 ++ Frontend/src/store/useToastStore.ts | 48 ++++++++++ 9 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 Frontend/src/components/ToastContainer.tsx create mode 100644 Frontend/src/pages/Guide.tsx create mode 100644 Frontend/src/store/useToastStore.ts diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index 04f30af..c75b70f 100755 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -6,6 +6,7 @@ import Footer from "./components/footer"; import { GameTick } from "./components/GameTick"; import { GameSync } from "./components/GameSync"; import { OfflineReport } from "./components/OfflineReport"; +import { ToastContainer } from "./components/ToastContainer"; import navData from "./data/NavBarData.json"; @@ -17,6 +18,7 @@ function App() { + = { + success: "border-emerald-500/40 bg-emerald-500/10", + info: "border-blue-400/40 bg-blue-400/10", + reward: "border-amber-400/40 bg-amber-400/10", + warning: "border-red-400/40 bg-red-400/10", +}; + +const VARIANT_ICONS: Record = { + success: "✓", + info: "ℹ", + reward: "★", + warning: "⚠", +}; + +const VARIANT_ICON_COLORS: Record = { + success: "text-emerald-400", + info: "text-blue-400", + reward: "text-amber-400", + warning: "text-red-400", +}; + +export function ToastContainer() { + const toasts = useToastStore((s) => s.toasts); + const remove = useToastStore((s) => s.removeToast); + + if (toasts.length === 0) return null; + + return ( +
+ {toasts.map((t) => ( +
remove(t.id)} + className={` + gp cursor-pointer border + ${VARIANT_STYLES[t.variant]} + animate-[slide-in_0.3s_ease-out] + `} + style={{ backdropFilter: "blur(12px)" }} + > +
+ + {VARIANT_ICONS[t.variant]} + + {t.message} +
+
+ ))} +
+ ); +} diff --git a/Frontend/src/data/NavBarData.json b/Frontend/src/data/NavBarData.json index 9ce56f8..1266361 100755 --- a/Frontend/src/data/NavBarData.json +++ b/Frontend/src/data/NavBarData.json @@ -10,5 +10,11 @@ "linkname": "Succès", "linkurl": "/achievements", "btn": false + }, + { + "id": "4", + "linkname": "Guide", + "linkurl": "/guide", + "btn": false } ] diff --git a/Frontend/src/index.css b/Frontend/src/index.css index 5c105e0..5d444ac 100755 --- a/Frontend/src/index.css +++ b/Frontend/src/index.css @@ -366,6 +366,17 @@ } } +@keyframes slide-in { + from { + opacity: 0; + transform: translateX(100%) scale(0.95); + } + to { + opacity: 1; + transform: translateX(0) scale(1); + } +} + @keyframes float-up { 0% { opacity: 1; diff --git a/Frontend/src/main.jsx b/Frontend/src/main.jsx index 6e3bf40..de8ffab 100755 --- a/Frontend/src/main.jsx +++ b/Frontend/src/main.jsx @@ -12,6 +12,7 @@ import Achievements from "./pages/Achievements"; import Settings from "./pages/Settings"; import Legal from "./pages/Legal"; import Cookie from "./pages/Cookie"; +import Guide from "./pages/Guide"; const router = createBrowserRouter([ { @@ -38,6 +39,10 @@ const router = createBrowserRouter([ path: "/cookies", element: , }, + { + path: "/guide", + element: , + }, { path: "/settings", element: , diff --git a/Frontend/src/pages/Guide.tsx b/Frontend/src/pages/Guide.tsx new file mode 100644 index 0000000..2552428 --- /dev/null +++ b/Frontend/src/pages/Guide.tsx @@ -0,0 +1,105 @@ +// Guide.tsx — Guide joueur in-game + +export default function Guide() { + return ( +
+

Guide du Gardien

+ +
+

Le Marais

+

+ Tu es le Gardien du Marais. Les tetards naissent sous tes clics, + grandissent grace a tes generateurs, et evoluent a chaque nouvelle generation. +

+
+ +
+

Boucle de jeu

+

+ 1. Clique pour pondre des tetards. Achete des generateurs (Nid, Mare, Marecage...) + qui produisent des tetards automatiquement. +

+

+ 2. Prestige quand tu atteins 1M de tetards. Tu perds tes tetards et generateurs, + mais tu gagnes de l'ADN Ancestral et un multiplicateur permanent. + Chaque generation est plus rapide que la precedente. +

+

+ 3. Arbre d'Evolution — depense ton ADN dans 3 branches : +

+
    +
  • Ponte — booste tes clics, double ponte, critiques
  • +
  • Marais — booste la production des generateurs
  • +
  • Adaptation — bonus offline, ADN bonus, seuil prestige reduit
  • +
+

+ Chaque branche a un capstone (noeud final puissant) et des + post-capstones achetables a l'infini pour une progression endless. +

+
+ +
+

Capstones

+
    +
  • Ponte Automatique — auto-click 1/s qui scale avec les upgrades
  • +
  • Symbiose Totale — chaque type de generateur booste les autres
  • +
  • Memoire du Marais — offline cap passe a 75%, duree 8h
  • +
+
+ +
+

Convergence

+

+ Quand tu as debloque un capstone + des noeuds d'une 2e branche, tu peux acheter + Convergence Alpha (+10% a tous les effets). + Avec 2 capstones, elle evolue en Convergence Omega (-20% cout post-capstones). +

+
+ +
+

Reset d'arbre

+

+ Tu peux reinitialiser ton arbre pour tester d'autres builds. + 1 reset gratuit par prestige, puis 5 ADN par reset supplementaire. + L'ADN investi est entierement rembourse. +

+
+ +
+

Milestones

+

+ 8 paliers de prestige (de 1 a 100) qui debloquent des cosmetiques exclusifs et + des bonus gameplay legers : +

+
    +
  • 1 prestige — Ruban queue
  • +
  • 3 prestiges — Titre "Gardien Recurrent"
  • +
  • 5 prestiges — 1 Nid gratuit au depart
  • +
  • 10 prestiges — Couronne doree
  • +
  • 15 prestiges — +5% offline permanent
  • +
  • 25 prestiges — Cape d'algues ancestrales
  • +
  • 50 prestiges — Queue enflamee + particules
  • +
  • 100 prestiges — Skin Tetard Primordial
  • +
+
+ +
+

Cosmetiques

+

+ Les cosmetiques sont purement visuels — zero pay-to-win. + Debloque-les via les achievements et les milestones prestige. + 5 slots : chapeau, yeux, corps, queue, accessoire. +

+
+ +
+

Offline

+

+ Quand tu fermes le jeu, le marais continue de produire. + Efficacite : 100% les 15 premieres minutes, puis degressive jusqu'a 0% a 2h. + Les noeuds d'arbre et milestones peuvent augmenter le cap offline. +

+
+
+ ); +} diff --git a/Frontend/src/pages/Home.jsx b/Frontend/src/pages/Home.jsx index f4cb265..5890090 100755 --- a/Frontend/src/pages/Home.jsx +++ b/Frontend/src/pages/Home.jsx @@ -160,7 +160,10 @@ export default function Home() { - {ACHIEVEMENTS.filter((a) => a.check(useGameStore.getState().state)).length}/{ACHIEVEMENTS.length} succès + {ACHIEVEMENTS.filter((a) => a.check(useGameStore.getState().state)).length}/{ACHIEVEMENTS.length} succes + + + Guide du Gardien diff --git a/Frontend/src/store/useGameStore.ts b/Frontend/src/store/useGameStore.ts index c05f732..fb49d47 100644 --- a/Frontend/src/store/useGameStore.ts +++ b/Frontend/src/store/useGameStore.ts @@ -25,6 +25,7 @@ import { offlineEfficiency, } from "../core/economy"; import { migrateSave } from "../core/migrateSave"; +import { toast } from "./useToastStore"; import { computeNewUnlocks, equipCosmetic as equipCosmeticFn, @@ -180,6 +181,7 @@ export const useGameStore = create((set, get) => ({ if (newUnlocks.length > 0) { const newCos = addToInventory(cosState, newUnlocks); updated.cosmeticInventory = newCos.inventory; + newUnlocks.forEach(() => toast("Nouveau cosmetique debloque !", "reward")); } } @@ -228,7 +230,11 @@ export const useGameStore = create((set, get) => ({ set((s) => { const updated = buyEvolutionNode(s.state, nodeId); if (!updated) return s; + const node = updated.evolutionTree.find((n) => n.id === nodeId); saveLocal(updated); + if (node?.capstone) { + toast(`Capstone debloque : ${node.name} !`, "reward", 5000); + } return { state: updated, productionPerSecond: totalProductionPerSecond(updated), @@ -242,6 +248,7 @@ export const useGameStore = create((set, get) => ({ if (!canPrestigeCheck(s.state)) return s; const updated = applyPrestige(s.state); saveLocal(updated); + toast(`Generation #${updated.prestigeCount} — Nouvelle vie !`, "success", 4000); return { state: updated, canPrestige: canPrestigeCheck(updated), @@ -305,6 +312,7 @@ export const useGameStore = create((set, get) => ({ const updated = claimMilestoneFn(s.state, milestoneId); if (!updated) return s; saveLocal(updated); + toast("Milestone debloque !", "reward", 4000); return { state: updated }; }); }, diff --git a/Frontend/src/store/useToastStore.ts b/Frontend/src/store/useToastStore.ts new file mode 100644 index 0000000..fdcb0d5 --- /dev/null +++ b/Frontend/src/store/useToastStore.ts @@ -0,0 +1,48 @@ +// useToastStore.ts — Toast notification system +// Stack de notifications auto-dismiss, utilisable partout via toast() + +import { create } from "zustand"; + +export type ToastVariant = "success" | "info" | "reward" | "warning"; + +export interface Toast { + id: number; + message: string; + variant: ToastVariant; + duration: number; // ms +} + +let nextId = 0; + +interface ToastStore { + toasts: Toast[]; + addToast: (message: string, variant?: ToastVariant, duration?: number) => void; + removeToast: (id: number) => void; +} + +export const useToastStore = create((set) => ({ + toasts: [], + + addToast: (message, variant = "info", duration = 3000) => { + const id = nextId++; + set((s) => ({ + toasts: [...s.toasts, { id, message, variant, duration }], + })); + setTimeout(() => { + set((s) => ({ + toasts: s.toasts.filter((t) => t.id !== id), + })); + }, duration); + }, + + removeToast: (id) => { + set((s) => ({ + toasts: s.toasts.filter((t) => t.id !== id), + })); + }, +})); + +// Shorthand pour utiliser depuis n'importe où (pas besoin du hook) +export function toast(message: string, variant?: ToastVariant, duration?: number) { + useToastStore.getState().addToast(message, variant, duration); +}