puzzle-trainer/app/patches/level/[n]/page.tsx
2026-05-23 01:05:21 +00:00

98 lines
4.5 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";
import Link from "next/link";
import { generatePatches, PatchesPuzzle } from "@/lib/generators/patches";
import PatchesBoard from "@/components/PatchesBoard";
import { levelToDate, levelMeta, TOTAL_LEVELS, GAME_META } from "@/lib/levels";
import { getGameProgress, recordLevelSolve } from "@/lib/progress";
const DIFF_COLORS: Record<number, { bg: string; text: string }> = {
1: { bg: "#f0fdf4", text: "#16a34a" },
2: { bg: "#fefce8", text: "#ca8a04" },
3: { bg: "#fff7ed", text: "#ea580c" },
4: { bg: "#fef2f2", text: "#dc2626" },
5: { bg: "#faf5ff", text: "#9333ea" },
};
function fmt(s: number) {
return `${String(Math.floor(s / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}`;
}
export default function PatchesLevelPage() {
const { n } = useParams<{ n: string }>();
const level = Math.max(1, Math.min(TOTAL_LEVELS, parseInt(n) || 1));
const { accent } = GAME_META["patches"];
const [puzzle, setPuzzle] = useState<PatchesPuzzle | null>(null);
const [completed, setCompleted] = useState(false);
const [bestTime, setBestTime] = useState(0);
useEffect(() => {
setPuzzle(generatePatches(levelToDate(level)));
const p = getGameProgress("patches");
const record = p[level];
setCompleted(!!record);
setBestTime(record?.bestTime ?? 0);
}, [level]);
const meta = levelMeta("patches", level);
const diffColors = DIFF_COLORS[meta.difficulty];
return (
<div className="flex flex-col items-center gap-6">
<div className="w-full flex items-center gap-1.5 text-xs text-gray-400">
<Link href="/patches" className="hover:text-gray-600 transition-colors">Patches</Link>
<span>/</span>
<Link href="/patches/levels" className="hover:text-gray-600 transition-colors">Niveaux</Link>
<span>/</span>
<span className="text-gray-600 font-medium">Niveau {level}</span>
</div>
<div className="text-center">
<div className="flex items-center justify-center gap-2 mb-2">
<h1 className="text-2xl font-bold text-gray-900 tracking-tight">Niveau {level}</h1>
{completed && (
<span className="flex items-center gap-1 text-xs font-semibold text-green-600 bg-green-50 px-2 py-0.5 rounded-full border border-green-200">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={3} strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
Complété
</span>
)}
</div>
<div className="flex items-center justify-center gap-2">
<span className="text-xs font-semibold px-2.5 py-1 rounded-full" style={{ background: diffColors.bg, color: diffColors.text }}>
{meta.difficultyLabel}
</span>
{completed && bestTime > 0 && (
<span className="text-xs text-gray-400 timer-mono"> {fmt(bestTime)}</span>
)}
</div>
</div>
{puzzle ? (
<PatchesBoard key={`level-patches-${level}`} puzzle={puzzle} date={`level-patches-${level}`} onSolve={(elapsed) => recordLevelSolve("patches", level, elapsed)} />
) : (
<div className="py-20 text-gray-300 text-sm">Chargement</div>
)}
<div className="flex items-center gap-3">
{level > 1 && (
<Link href={`/patches/level/${level - 1}`} className="flex items-center gap-1 px-4 py-2 rounded-full border border-gray-200 text-gray-500 text-sm hover:border-gray-300 hover:text-gray-700 transition-colors">
<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>
Niveau {level - 1}
</Link>
)}
<Link href="/patches/levels" className="px-4 py-2 rounded-full border border-gray-200 text-gray-500 text-sm hover:border-gray-300 hover:text-gray-700 transition-colors">
Tous les niveaux
</Link>
{level < TOTAL_LEVELS && (
<Link href={`/patches/level/${level + 1}`} className="flex items-center gap-1 px-4 py-2 rounded-full text-white text-sm font-semibold transition-opacity hover:opacity-90" style={{ background: accent }}>
Niveau {level + 1}
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round"><polyline points="9 18 15 12 9 6"/></svg>
</Link>
)}
</div>
</div>
);
}