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

125 lines
4.9 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";
import Link from "next/link";
import { generateQueens, QueensPuzzle } from "@/lib/generators/queens";
import QueensBoard from "@/components/QueensBoard";
import { levelToDate, queensSizeForLevel, 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 QueensLevelPage() {
const { n } = useParams<{ n: string }>();
const level = Math.max(1, Math.min(TOTAL_LEVELS, parseInt(n) || 1));
const { accent } = GAME_META["queens"];
const [puzzle, setPuzzle] = useState<QueensPuzzle | null>(null);
const [completed, setCompleted] = useState(false);
const [bestTime, setBestTime] = useState(0);
useEffect(() => {
const date = levelToDate(level);
const size = queensSizeForLevel(level);
setPuzzle(generateQueens(date, size));
const p = getGameProgress("queens");
const record = p[level];
setCompleted(!!record);
setBestTime(record?.bestTime ?? 0);
}, [level]);
const meta = levelMeta("queens", level);
const diffColors = DIFF_COLORS[meta.difficulty];
return (
<div className="flex flex-col items-center gap-6">
{/* Breadcrumb */}
<div className="w-full flex items-center gap-1.5 text-xs text-gray-400">
<Link href="/queens" className="hover:text-gray-600 transition-colors">Queens</Link>
<span>/</span>
<Link href="/queens/levels" className="hover:text-gray-600 transition-colors">Niveaux</Link>
<span>/</span>
<span className="text-gray-600 font-medium">Niveau {level}</span>
</div>
{/* Header */}
<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 ? (
<QueensBoard
key={`level-queens-${level}`}
puzzle={puzzle}
date={`level-queens-${level}`}
onSolve={(elapsed) => recordLevelSolve("queens", level, elapsed)}
/>
) : (
<div className="py-20 text-gray-300 text-sm">Chargement</div>
)}
{/* Level navigation */}
<div className="flex items-center gap-3">
{level > 1 && (
<Link
href={`/queens/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="/queens/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={`/queens/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>
);
}