puzzle-trainer/components/LevelsPageShell.tsx
2026-05-23 01:05:21 +00:00

101 lines
4.1 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import LevelGrid from "@/components/LevelGrid";
import { GameProgress, getGameProgress, nextLevel, allStats } from "@/lib/progress";
import { GameId, GAME_META, TOTAL_LEVELS } from "@/lib/levels";
interface Props {
game: GameId;
}
function fmt(s: number) {
return `${String(Math.floor(s / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}`;
}
export default function LevelsPageShell({ game }: Props) {
const [progress, setProgress] = useState<GameProgress>({});
const [next, setNext] = useState(1);
const { name, accent } = GAME_META[game];
useEffect(() => {
const refresh = () => {
setProgress(getGameProgress(game));
setNext(nextLevel(game));
};
refresh();
window.addEventListener("focus", refresh);
document.addEventListener("visibilitychange", refresh);
return () => {
window.removeEventListener("focus", refresh);
document.removeEventListener("visibilitychange", refresh);
};
}, [game]);
const stats = allStats();
const gameStats = stats[game];
const completedCount = Object.keys(progress).length;
const pct = Math.round((completedCount / TOTAL_LEVELS) * 100);
return (
<div className="flex flex-col items-center gap-8 max-w-lg mx-auto">
{/* Header */}
<div className="w-full flex items-start justify-between">
<div>
<div className="flex items-center gap-2 mb-1">
<Link href={`/${game}`} className="text-sm text-gray-400 hover:text-gray-600 transition-colors flex items-center gap-1">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round"><polyline points="15 18 9 12 15 6"/></svg>
{name}
</Link>
</div>
<h1 className="text-2xl font-bold text-gray-900 tracking-tight">{name} Niveaux</h1>
<p className="text-sm text-gray-400 mt-1">100 puzzles · difficulté progressive</p>
</div>
{/* Completion ring */}
<div className="flex flex-col items-end gap-0.5 shrink-0 mt-1">
<span className="text-2xl font-black tabular-nums" style={{ color: accent }}>
{completedCount}
</span>
<span className="text-xs text-gray-400">/ {TOTAL_LEVELS}</span>
{pct > 0 && (
<span className="text-[10px] font-semibold text-gray-300">{pct}%</span>
)}
</div>
</div>
{/* Stats strip */}
{gameStats && gameStats.bestTime > 0 && (
<div className="w-full flex gap-3">
<div className="flex-1 flex flex-col items-center gap-0.5 px-3 py-2.5 bg-white rounded-xl border border-gray-100">
<span className="text-xs text-gray-400">Meilleur temps</span>
<span className="text-base font-bold text-gray-800 timer-mono">{fmt(gameStats.bestTime)}</span>
</div>
<div className="flex-1 flex flex-col items-center gap-0.5 px-3 py-2.5 bg-white rounded-xl border border-gray-100">
<span className="text-xs text-gray-400">Prochain</span>
<span className="text-base font-bold text-gray-800">Niv. {next}</span>
</div>
<div className="flex-1 flex flex-col items-center gap-0.5 px-3 py-2.5 bg-white rounded-xl border border-gray-100">
<span className="text-xs text-gray-400">Complétés</span>
<span className="text-base font-bold text-gray-800">{completedCount}</span>
</div>
</div>
)}
{/* CTA */}
<Link
href={`/${game}/level/${next}`}
className="w-full flex items-center justify-center gap-2 py-3 rounded-2xl text-white font-semibold text-base transition-opacity hover:opacity-90 shadow-sm"
style={{ background: accent }}
>
{completedCount === 0 ? "Commencer" : "Continuer"} Niveau {next}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} strokeLinecap="round"><polyline points="9 18 15 12 9 6"/></svg>
</Link>
{/* Grid */}
<LevelGrid game={game} progress={progress} currentLevel={next} />
</div>
);
}