98 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
}
|