81 lines
2.6 KiB
TypeScript
81 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
import { GAME_META, GAMES, GameId, TOTAL_LEVELS } from "@/lib/levels";
|
|
|
|
function getLevelProgress(game: GameId): number {
|
|
if (typeof window === "undefined") return 0;
|
|
try {
|
|
const raw = localStorage.getItem(`levels-${game}`);
|
|
if (!raw) return 0;
|
|
const data = JSON.parse(raw);
|
|
return data.maxUnlocked ?? 0;
|
|
} catch { return 0; }
|
|
}
|
|
|
|
function GameCard({ game }: { game: GameId }) {
|
|
const meta = GAME_META[game];
|
|
const [progress, setProgress] = useState(0);
|
|
|
|
useEffect(() => {
|
|
setProgress(getLevelProgress(game));
|
|
}, [game]);
|
|
|
|
const pct = Math.round((progress / TOTAL_LEVELS) * 100);
|
|
|
|
return (
|
|
<Link
|
|
href={`/${game}/levels`}
|
|
className="block bg-white rounded-2xl p-4 shadow-sm border border-gray-100 active:scale-[0.98] transition-transform"
|
|
>
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<span className="text-2xl" style={{ fontFamily: "monospace" }}>{meta.symbol}</span>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="font-bold text-gray-900 text-base leading-tight">{meta.name}</p>
|
|
<p className="text-xs text-gray-400 truncate">{meta.subtitle}</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="text-sm font-bold" style={{ color: meta.accent }}>{progress}</p>
|
|
<p className="text-[10px] text-gray-400">/ {TOTAL_LEVELS}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress bar */}
|
|
<div className="h-1.5 bg-gray-100 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full rounded-full transition-all duration-500"
|
|
style={{ width: `${pct}%`, backgroundColor: meta.accent }}
|
|
/>
|
|
</div>
|
|
<div className="flex justify-between mt-1">
|
|
<p className="text-[10px] text-gray-400">{meta.duration}</p>
|
|
<p className="text-[10px] text-gray-400">{pct}% complété</p>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export default function LevelsHubPage() {
|
|
return (
|
|
<div className="px-4 pb-6">
|
|
{/* Header */}
|
|
<div className="pt-4 pb-5">
|
|
<h1 className="text-2xl font-bold text-gray-900">Entraînement</h1>
|
|
<p className="text-sm text-gray-500 mt-0.5">100 niveaux par jeu, du facile à l'expert</p>
|
|
</div>
|
|
|
|
{/* Game cards */}
|
|
<div className="flex flex-col gap-3">
|
|
{GAMES.map(game => (
|
|
<GameCard key={game} game={game} />
|
|
))}
|
|
</div>
|
|
|
|
{/* Footer hint */}
|
|
<p className="text-center text-xs text-gray-400 mt-6">
|
|
Résous les niveaux du quotidien pour débloquer les suivants
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|