From 9065b1c593772c503d003a56e874322ceb4ae6c1 Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Fri, 20 Mar 2026 16:03:59 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20cockpit=20sidebar=20=E2=80=94=20design?= =?UTF-8?q?=20tokens,=20panels=20harmonis=C3=A9s,=20header=20stats=20fixe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Design tokens game dans root.scss (--gp-*) — un seul endroit pour thémiser - game-panels.scss : classes partagées (gp, gp-row, gp-btn, gp-progress, etc.) - CockpitHeader : dashboard résumé (prod/s, ponte, mult, ADN, génération) - Tous les panels refactorisés sur le système gp-* (tailles, couleurs, spacing) - Sidebar structurée en zones : header → progression → générateurs → prestige → évolution --- Frontend/src/components/CockpitHeader.tsx | 37 ++++ Frontend/src/components/EvolutionTree.tsx | 94 ++++------ Frontend/src/components/GeneratorShop.tsx | 34 ++-- Frontend/src/components/MilestoneBar.tsx | 24 +-- Frontend/src/components/PrestigePanel.tsx | 34 +--- Frontend/src/pages/Home.jsx | 19 +- Frontend/src/scss/components/game-panels.scss | 172 ++++++++++++++++++ Frontend/src/scss/home.scss | 30 +-- Frontend/src/scss/root.scss | 29 +++ 9 files changed, 321 insertions(+), 152 deletions(-) create mode 100644 Frontend/src/components/CockpitHeader.tsx create mode 100644 Frontend/src/scss/components/game-panels.scss diff --git a/Frontend/src/components/CockpitHeader.tsx b/Frontend/src/components/CockpitHeader.tsx new file mode 100644 index 0000000..44884b3 --- /dev/null +++ b/Frontend/src/components/CockpitHeader.tsx @@ -0,0 +1,37 @@ +// CockpitHeader.tsx — Dashboard résumé toujours visible en haut du cockpit + +import { useGameStore } from "../store/useGameStore"; +import { formatNumber } from "../utils/formatNumber"; + +export function CockpitHeader() { + const productionPerSecond = useGameStore((s) => s.productionPerSecond); + const { clickMultiplier, prestigeMultiplier, ancestralDna, prestigeCount } = + useGameStore((s) => s.state); + + return ( +
+
+ Prod/s + + {formatNumber(productionPerSecond)} + +
+
+ Ponte + x{clickMultiplier} +
+
+ Mult + x{prestigeMultiplier.toFixed(1)} +
+
+ ADN + {ancestralDna} +
+
+ Gén. + {prestigeCount} +
+
+ ); +} diff --git a/Frontend/src/components/EvolutionTree.tsx b/Frontend/src/components/EvolutionTree.tsx index 1afec05..1a913b1 100644 --- a/Frontend/src/components/EvolutionTree.tsx +++ b/Frontend/src/components/EvolutionTree.tsx @@ -1,20 +1,18 @@ // 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> = { - 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`, +const EFFECT_LABELS: Record string> = { + click_multiplier: (v) => `x${v} ponte`, + production_multiplier: (v) => `x${v} production`, + start_bonus: (v) => `+${v} têtards au départ`, + unlock_generator: () => `Lac Mystique dès le début`, + achievement_scaling: (v) => `+${(v * 100).toFixed(0)}% prod/succès`, }; -function NodeCard({ +function NodeRow({ node, canBuy, onBuy, @@ -23,36 +21,28 @@ function NodeCard({ canBuy: boolean; onBuy: () => void; }) { + const rowClass = node.unlocked + ? "gp-row gp-row--unlocked" + : canBuy + ? "gp-row gp-row--evolution" + : "gp-row gp-row--locked"; + return ( -
-
- {node.name} - - {node.unlocked ? "Débloqué" : `${node.cost} ADN`} - +
+
+ {node.name} + {EFFECT_LABELS[node.effect](node.value)}
-

- {EFFECT_DESCRIPTIONS[node.effect](node.value)} -

- {!node.unlocked && ( + {node.unlocked ? ( + OK + ) : ( )}
@@ -67,33 +57,19 @@ export function EvolutionTree() { if (prestigeCount < 1) return null; return ( -
-
-

Arbre d'Évolution

- {state.ancestralDna} ADN -
-
- {evolutionTree.map((node, index) => ( - - {index > 0 && ( -
- | -
- )} - buyNode(node.id)} - /> -
- ))} +
+
+ Évolution + {state.ancestralDna} ADN
+ {evolutionTree.map((node) => ( + buyNode(node.id)} + /> + ))}
); } diff --git a/Frontend/src/components/GeneratorShop.tsx b/Frontend/src/components/GeneratorShop.tsx index 1817d01..135c7ae 100644 --- a/Frontend/src/components/GeneratorShop.tsx +++ b/Frontend/src/components/GeneratorShop.tsx @@ -11,12 +11,10 @@ export function GeneratorShop() { const generatorCost = useGameStore((s) => s.generatorCost); return ( -
-
-

Générateurs

- - {formatNumber(productionPerSecond)}/s - +
+
+ Générateurs + {formatNumber(productionPerSecond)}/s
{generators.map((gen) => { const cost = generatorCost(gen); @@ -26,32 +24,24 @@ export function GeneratorShop() { return (
-
-
- {gen.name} +
+
+ {gen.name} {gen.owned > 0 && ( - x{gen.owned} + x{gen.owned} )}
- - +{gen.baseProduction}/s chacun + + +{gen.baseProduction}/s {gen.owned > 0 && ` · ${formatNumber(currentProd)}/s total`}
diff --git a/Frontend/src/components/MilestoneBar.tsx b/Frontend/src/components/MilestoneBar.tsx index e16ec1b..a689256 100644 --- a/Frontend/src/components/MilestoneBar.tsx +++ b/Frontend/src/components/MilestoneBar.tsx @@ -1,5 +1,4 @@ // MilestoneBar.tsx — Progression vers le prochain prestige -// Barre visuelle ressources / 1 000 000 import { useGameStore } from "../store/useGameStore"; import { formatNumber } from "../utils/formatNumber"; @@ -14,24 +13,27 @@ export function MilestoneBar() { const remaining = Math.max(PRESTIGE_THRESHOLD - resources, 0); return ( -
-
- Prochaine Génération - +
+
+ Prochaine Génération + {formatNumber(resources)} / {formatNumber(PRESTIGE_THRESHOLD)}
-
+
-
+ {remaining > 0 - ? `${formatNumber(remaining)} têtards restants` + ? `${formatNumber(remaining)} restants` : "Nouvelle Génération disponible !"} -
+
); } diff --git a/Frontend/src/components/PrestigePanel.tsx b/Frontend/src/components/PrestigePanel.tsx index f221b2f..a789200 100644 --- a/Frontend/src/components/PrestigePanel.tsx +++ b/Frontend/src/components/PrestigePanel.tsx @@ -1,12 +1,10 @@ // PrestigePanel.tsx — Nouvelle Génération (prestige) -// Visible uniquement quand canPrestige = true (ressources >= 1 000 000) import { useGameStore } from "../store/useGameStore"; import { computePrestigeDna } from "../core/economy"; export function PrestigePanel() { - const { prestigeCount, prestigeMultiplier, ancestralDna, lifetimeTadpoles } = - useGameStore((s) => s.state); + const { lifetimeTadpoles } = useGameStore((s) => s.state); const canPrestige = useGameStore((s) => s.canPrestige); const prestige = useGameStore((s) => s.prestige); @@ -18,40 +16,26 @@ export function PrestigePanel() { `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 ?` + `Confirmer ?` ); if (confirmed) prestige(); }; return ( -
-

Prestige

-
- Gén. {prestigeCount} - Mult x{prestigeMultiplier.toFixed(1)} - ADN {ancestralDna} -
- +
+ Prestige {canPrestige ? ( -
-

+

+ +{dnaPreview} ADN · +0.1x mult -

-
) : ( -

- Atteins 1M têtards pour prestige -

+ Atteins 1M têtards pour prestige )}
); diff --git a/Frontend/src/pages/Home.jsx b/Frontend/src/pages/Home.jsx index f01d022..32c3068 100755 --- a/Frontend/src/pages/Home.jsx +++ b/Frontend/src/pages/Home.jsx @@ -8,17 +8,16 @@ import { GeneratorShop } from "../components/GeneratorShop"; import { PrestigePanel } from "../components/PrestigePanel"; import { EvolutionTree } from "../components/EvolutionTree"; import { MilestoneBar } from "../components/MilestoneBar"; +import { CockpitHeader } from "../components/CockpitHeader"; import { ACHIEVEMENTS } from "../data/achievements"; import "../scss/home.scss"; +import "../scss/components/game-panels.scss"; export default function Home() { const [toggleRain] = useOutletContext(); const click = useGameStore((s) => s.click); const resources = useGameStore((s) => s.state.resources); const clickMultiplier = useGameStore((s) => s.state.clickMultiplier); - const productionPerSecond = useGameStore((s) => s.productionPerSecond); - const state = useGameStore((s) => s.state); - const prestigeMultiplier = state.prestigeMultiplier; const createParticle = useCallback((clientX, clientY) => { const particle = document.createElement("span"); @@ -122,24 +121,20 @@ export default function Home() {
{formatNumber(resources)}
-
- {formatNumber(productionPerSecond)}/s - · - x{clickMultiplier} ponte - · - x{prestigeMultiplier.toFixed(1)} mult -
- {/* Game panels — sidebar (right desktop, bottom mobile) */} + {/* Cockpit — sidebar structurée en zones */} diff --git a/Frontend/src/scss/components/game-panels.scss b/Frontend/src/scss/components/game-panels.scss new file mode 100644 index 0000000..6f2b30b --- /dev/null +++ b/Frontend/src/scss/components/game-panels.scss @@ -0,0 +1,172 @@ +// game-panels.scss — Système de style partagé pour tous les panels du cockpit +// Modifier les tokens dans root.scss, pas ici. + +// --- Panel de base --- +.gp { + display: flex; + flex-direction: column; + gap: var(--gp-gap); + padding: var(--gp-padding); + background: var(--gp-bg); + backdrop-filter: blur(8px); + border: 1px solid var(--gp-border); + border-radius: var(--gp-radius); +} + +.gp-title { + font-family: var(--font); + font-size: var(--gp-title); + font-weight: 700; + color: var(--gp-text-color); + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.gp-label { + font-family: var(--font); + font-size: var(--gp-text-sm); + font-weight: 500; + color: var(--gp-text-muted); +} + +.gp-value { + font-family: var(--font); + font-size: var(--gp-text); + font-weight: 600; + color: var(--gp-text-color); +} + +.gp-accent-green { color: var(--gp-accent-green); } +.gp-accent-purple { color: var(--gp-accent-purple); } +.gp-accent-amber { color: var(--gp-accent-amber); } + +// --- Row item (générateur, noeud évolution) --- +.gp-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.4rem; + padding: 0.4rem 0.5rem; + border-radius: calc(var(--gp-radius) - 0.15rem); + border: 1px solid transparent; + transition: background 0.15s ease, border-color 0.15s ease; +} + +.gp-row--active { + border-color: rgba(16, 185, 129, 0.3); + background: var(--gp-accent-green-bg); + + &:hover { + background: rgba(16, 185, 129, 0.18); + } +} + +.gp-row--locked { + border-color: var(--gp-border); + background: rgba(255, 255, 255, 0.02); + opacity: 0.5; +} + +.gp-row--evolution { + border-color: rgba(251, 191, 36, 0.3); + background: var(--gp-accent-amber-bg); +} + +.gp-row--unlocked { + border-color: rgba(16, 185, 129, 0.3); + background: var(--gp-accent-green-bg); +} + +// --- Bouton achat --- +.gp-btn { + font-family: var(--font); + font-size: var(--gp-text-sm); + font-weight: 600; + padding: 0.3rem 0.6rem; + border-radius: 0.4rem; + border: none; + cursor: pointer; + transition: background 0.15s ease; + white-space: nowrap; +} + +.gp-btn--buy { + background: var(--gp-btn-bg); + color: white; + + &:hover { + background: var(--gp-btn-bg-hover); + } +} + +.gp-btn--disabled { + background: var(--gp-btn-disabled); + color: var(--gp-btn-text-disabled); + cursor: not-allowed; +} + +.gp-btn--prestige { + background: #7c3aed; + color: white; + padding: 0.4rem 0.8rem; + font-size: var(--gp-text); + animation: gp-pulse 2s ease-in-out infinite; + + &:hover { + background: #8b5cf6; + } +} + +@keyframes gp-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(124, 58, 237, 0.4); } + 50% { box-shadow: 0 0 0 6px rgba(124, 58, 237, 0); } +} + +// --- Header cockpit (stats résumé) --- +.gp-cockpit-header { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 0.3rem; +} + +.gp-stat { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.1rem; + min-width: 3.5rem; +} + +// --- Progress bar --- +.gp-progress { + height: 0.35rem; + background: rgba(255, 255, 255, 0.08); + border-radius: 1rem; + overflow: hidden; +} + +.gp-progress-fill { + height: 100%; + border-radius: 1rem; + transition: width 0.5s ease; +} + +// --- Section separator --- +.gp-sep { + height: 1px; + background: var(--gp-border); + margin: 0.15rem 0; +} + +// --- Zone titles in sidebar --- +.gp-zone-label { + font-family: var(--font); + font-size: var(--gp-text-sm); + font-weight: 600; + color: var(--gp-text-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + padding-left: 0.2rem; +} diff --git a/Frontend/src/scss/home.scss b/Frontend/src/scss/home.scss index a97de3b..bf1e172 100755 --- a/Frontend/src/scss/home.scss +++ b/Frontend/src/scss/home.scss @@ -47,45 +47,29 @@ } } -// --- Stats bar sous le compteur --- +// --- Stats sous le compteur --- .click-zone-stats { display: flex; flex-direction: column; align-items: center; - gap: 0.3rem; pointer-events: none; user-select: none; } -.stats-bar { - display: flex; - align-items: center; - gap: 0.4rem; - font-family: var(--font); - font-size: 0.85rem; - font-weight: 500; - color: rgba(255, 255, 255, 0.7); - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); -} - -.stats-sep { - opacity: 0.4; -} - // --- Badge achievements sidebar --- .achieve-badge { display: block; text-align: center; - padding: 0.5rem; - border-radius: 0.5rem; - background: rgba(16, 185, 129, 0.1); + padding: 0.4rem; + border-radius: var(--gp-radius); + background: var(--gp-accent-green-bg); border: 1px solid rgba(16, 185, 129, 0.2); font-family: var(--font); - font-size: 0.8rem; - font-weight: 500; - color: #6ee7b7; + font-size: var(--gp-text-sm); + font-weight: 600; + color: var(--gp-accent-green); text-decoration: none; transition: all 0.15s ease; diff --git a/Frontend/src/scss/root.scss b/Frontend/src/scss/root.scss index aed1737..051dea7 100755 --- a/Frontend/src/scss/root.scss +++ b/Frontend/src/scss/root.scss @@ -17,6 +17,35 @@ --bg-color: var(--color-blue-light); --font: "Hanken Grotesk", sans-serif; + + // --- Game panel tokens --- + --gp-bg: rgba(17, 17, 17, 0.75); + --gp-bg-hover: rgba(17, 17, 17, 0.85); + --gp-border: rgba(255, 255, 255, 0.08); + --gp-radius: 0.75rem; + --gp-padding: 0.75rem; + --gp-gap: 0.5rem; + + // Text + --gp-title: 0.8rem; + --gp-text: 0.75rem; + --gp-text-sm: 0.65rem; + --gp-text-color: rgba(255, 255, 255, 0.9); + --gp-text-muted: rgba(255, 255, 255, 0.5); + + // Accent colors + --gp-accent-green: #34d399; + --gp-accent-purple: #a78bfa; + --gp-accent-amber: #fbbf24; + --gp-accent-green-bg: rgba(16, 185, 129, 0.12); + --gp-accent-purple-bg: rgba(139, 92, 246, 0.12); + --gp-accent-amber-bg: rgba(251, 191, 36, 0.12); + + // Buttons + --gp-btn-bg: #059669; + --gp-btn-bg-hover: #10b981; + --gp-btn-disabled: rgba(255, 255, 255, 0.08); + --gp-btn-text-disabled: rgba(255, 255, 255, 0.3); } a {