Résumé de partie : détail des manches + icônes de catégories
Build and Publish Docker Image / build-and-push-image (push) Successful in 59s

- GameOver : nouvelle section "Détail des manches" (tableau manches × joueurs
  avec ligne Total) pour les parties multi-manches ; indicateurs Cabo/Kamikaze
- ScoringCategory : champs icon (lucide) + color pour un visuel par catégorie
- Harmonies : icônes + couleurs de terrain (arbres, montagnes, champs,
  bâtiments, rivière, animaux, esprits) affichées dans PlayGame et GameOver

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Zed
2026-07-12 20:56:45 +02:00
parent c69a5195f5
commit 90fba52a92
4 changed files with 129 additions and 14 deletions
+8 -6
View File
@@ -1,17 +1,19 @@
import { GameConfig, ScoringCategory } from "../../types"; import { GameConfig, ScoringCategory } from "../../types";
const BASE_CATEGORIES: ScoringCategory[] = [ const BASE_CATEGORIES: ScoringCategory[] = [
{ id: "arbres", label: "Arbres" }, { id: "arbres", label: "Arbres", icon: "TreePine", color: "#16a34a" },
{ id: "montagnes", label: "Montagnes" }, { id: "montagnes", label: "Montagnes", icon: "Mountain", color: "#64748b" },
{ id: "champs", label: "Champs" }, { id: "champs", label: "Champs", icon: "Wheat", color: "#d97706" },
{ id: "batiments", label: "Bâtiments" }, { id: "batiments", label: "Bâtiments", icon: "Building2", color: "#dc2626" },
{ id: "eau", label: "Rivière" }, { id: "eau", label: "Rivière", icon: "Waves", color: "#0ea5e9" },
{ id: "animaux", label: "Animaux" }, { id: "animaux", label: "Animaux", icon: "Rabbit", color: "#a16207" },
]; ];
const NATURE_SPIRIT_CATEGORY: ScoringCategory = { const NATURE_SPIRIT_CATEGORY: ScoringCategory = {
id: "esprits_nature", id: "esprits_nature",
label: "Esprits de la nature", label: "Esprits de la nature",
icon: "Sparkles",
color: "#8b5cf6",
}; };
export const harmonieConfig: GameConfig = { export const harmonieConfig: GameConfig = {
+94 -1
View File
@@ -137,7 +137,7 @@ export default function GameOver() {
const topWinner = totals.find((t) => t.isWinner)?.player; const topWinner = totals.find((t) => t.isWinner)?.player;
return ( return (
<div className="flex flex-col min-h-screen bg-background pb-24"> <div className="flex flex-col min-h-screen bg-background pb-40">
{/* 💥 Animation de Célébration (disparaît après qq secondes) */} {/* 💥 Animation de Célébration (disparaît après qq secondes) */}
{showCelebration && topWinner && ( {showCelebration && topWinner && (
<CelebrationOverlay winner={topWinner} gameId={gameConfig?.id || ""} /> <CelebrationOverlay winner={topWinner} gameId={gameConfig?.id || ""} />
@@ -242,11 +242,21 @@ export default function GameOver() {
{categories.map((cat) => { {categories.map((cat) => {
const val = t.details?.[cat.id]; const val = t.details?.[cat.id];
if (!val || val === "0") return null; if (!val || val === "0") return null;
const CatIcon = cat.icon
? (Icons as any)[cat.icon]
: null;
return ( return (
<div <div
key={cat.id} key={cat.id}
className="flex flex-col items-center bg-secondary/30 rounded-lg p-1.5" className="flex flex-col items-center bg-secondary/30 rounded-lg p-1.5"
> >
{CatIcon && (
<CatIcon
className="w-4 h-4 mb-0.5"
style={{ color: cat.color ?? "currentColor" }}
strokeWidth={2.2}
/>
)}
<span className="text-[10px] text-muted-foreground truncate w-full text-center"> <span className="text-[10px] text-muted-foreground truncate w-full text-center">
{cat.label} {cat.label}
</span> </span>
@@ -262,6 +272,89 @@ export default function GameOver() {
</motion.div> </motion.div>
))} ))}
</div> </div>
{/* Round-by-round detail (multi-round games) */}
{session.rounds.length > 1 && (
<section className="pt-2">
<h2 className="text-xl font-semibold px-2 mb-2">
Détail des manches
</h2>
<Card className="overflow-hidden">
<CardContent className="p-0 overflow-x-auto">
<table className="w-full text-sm border-collapse">
<thead>
<tr className="border-b border-border/60">
<th className="text-left font-black text-muted-foreground px-3 py-2.5 sticky left-0 bg-card z-10">
Manche
</th>
{session.players.map((p) => (
<th
key={p.id}
className="px-3 py-2.5 text-right font-bold whitespace-nowrap"
>
{p.name}
</th>
))}
</tr>
</thead>
<tbody>
{session.rounds.map((round) => (
<tr
key={round.id}
className="border-b border-border/30 last:border-0"
>
<td className="px-3 py-2 font-bold text-muted-foreground sticky left-0 bg-card z-10">
#{round.roundNumber}
</td>
{session.players.map((p) => {
const s = round.scores.find(
(sc) => sc.playerId === p.id,
);
return (
<td
key={p.id}
className="px-3 py-2 text-right tabular-nums"
>
{s ? s.score : "—"}
{s?.caboCall === "success" && (
<span className="ml-1 text-[10px] text-green-600 font-bold">
</span>
)}
{s?.caboCall === "fail" && (
<span className="ml-1 text-[10px] text-destructive font-bold">
</span>
)}
{s?.kamikaze && (
<span className="ml-1 text-[10px] text-amber-600 font-bold">
K
</span>
)}
</td>
);
})}
</tr>
))}
<tr className="bg-secondary/50 font-black">
<td className="px-3 py-2.5 sticky left-0 bg-secondary/50 z-10">
Total
</td>
{session.players.map((p) => (
<td
key={p.id}
className="px-3 py-2.5 text-right tabular-nums text-primary"
>
{calculateTotalScore(p.id)}
</td>
))}
</tr>
</tbody>
</table>
</CardContent>
</Card>
</section>
)}
</div> </div>
<div className="fixed bottom-0 left-0 right-0 p-4 bg-background/95 backdrop-blur-md border-t flex flex-wrap gap-3 justify-center max-w-md mx-auto"> <div className="fixed bottom-0 left-0 right-0 p-4 bg-background/95 backdrop-blur-md border-t flex flex-wrap gap-3 justify-center max-w-md mx-auto">
+19 -1
View File
@@ -322,13 +322,31 @@ export default function PlayGame() {
{/* Current Round Input Section */} {/* Current Round Input Section */}
<section className="relative"> <section className="relative">
<div className="flex items-center justify-between px-2 mb-4"> <div className="flex items-center justify-between px-2 mb-4">
<h2 className="text-2xl font-black tracking-tighter drop-shadow-sm"> <div className="flex items-center gap-2.5 min-w-0">
{categories &&
(() => {
const cat = categories[currentCategoryIndex];
const CatIcon = cat.icon ? (Icons as any)[cat.icon] : null;
return CatIcon ? (
<span
className="w-11 h-11 rounded-2xl grid place-items-center shrink-0 shadow-sm"
style={{
backgroundColor: `${cat.color ?? "#14b8a6"}22`,
color: cat.color ?? "#14b8a6",
}}
>
<CatIcon className="w-6 h-6" strokeWidth={2.2} />
</span>
) : null;
})()}
<h2 className="text-2xl font-black tracking-tighter drop-shadow-sm truncate">
{categories {categories
? categories[currentCategoryIndex].label ? categories[currentCategoryIndex].label
: isSingleRound : isSingleRound
? "Saisie des scores" ? "Saisie des scores"
: "À vos marques !"} : "À vos marques !"}
</h2> </h2>
</div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{categories && currentCategoryIndex > 0 && ( {categories && currentCategoryIndex > 0 && (
<Button <Button
+2
View File
@@ -4,6 +4,8 @@ export type ScoreType =
export interface ScoringCategory { export interface ScoringCategory {
id: string; id: string;
label: string; label: string;
icon?: string; // nom d'icône lucide (ex: "TreePine") — visuel de la catégorie
color?: string; // teinte d'accent hex pour l'icône
} }
export interface GameConfig { export interface GameConfig {