"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import { todayISO } from "@/lib/rng"; import { GAME_META, GAMES, GameId } from "@/lib/levels"; import { allStats, GameStats } from "@/lib/progress"; import { loadStats, updateRitualStreak, getRitualStreak, RitualStats } from "@/lib/stats"; import { PlayMode, getPlayMode, savePlayMode, getSessionOrder } from "@/lib/session"; // ── Other helpers ───────────────────────────────────────────────────────────── function fmt(s: number): string { return `${String(Math.floor(s / 60)).padStart(2, "0")}:${String(s % 60).padStart(2, "0")}`; } function isDailySolved(game: GameId, date: string): boolean { if (typeof window === "undefined") return false; try { const stats = localStorage.getItem(`stats-${game}`); if (!stats) return false; return JSON.parse(stats)?.lastDate === date; } catch { return false; } } function isNewUser(): boolean { if (typeof window === "undefined") return true; return GAMES.every(g => !localStorage.getItem(`stats-${g}`)); } // ── Onboarding screen ───────────────────────────────────────────────────────── function OnboardingScreen() { return (
🧩

Puzzle Trainer

5 puzzles logiques, chaque jour.

🔓 Pas de compte, pas de pub
📱 Tout dans le navigateur
2 à 4 minutes par puzzle
Commencer

Commence avec Tango — le plus facile à apprendre

); } // ── Mode toggle ─────────────────────────────────────────────────────────────── function ModeToggle({ mode, onChange }: { mode: PlayMode; onChange: (m: PlayMode) => void }) { return (
); } // ── Streak banner ───────────────────────────────────────────────────────────── function StreakBanner({ ritual, today }: { ritual: RitualStats; today: string }) { if (ritual.streak === 0) return null; const isAliveToday = ritual.lastDate === today; return (
🔥

{ritual.streak} jour{ritual.streak > 1 ? "s" : ""} de suite

{isAliveToday ? "Tous les puzzles complétés aujourd'hui !" : "Complète les puzzles du jour pour continuer ta série"}

); } // ── Free mode: simple game row ──────────────────────────────────────────────── function FreeRow({ game, solved, lastTime }: { game: GameId; solved: boolean; lastTime: number; }) { const { name, accent, subtitle, duration, symbol } = GAME_META[game]; return ( {solved ? "✓" : symbol}
{name} {!solved && {duration}}

{solved && lastTime > 0 ? `Résolu en ${fmt(lastTime)}` : subtitle}

{solved ? ( Rejouer ) : ( Jouer )} ); } // ── Session mode: ordered list with progress ────────────────────────────────── function SessionList({ order, solvedToday, lastTimes }: { order: GameId[]; solvedToday: Record; lastTimes: Record; }) { // Find the next game to play (first unsolved in order) const nextIdx = order.findIndex(g => !solvedToday[g]); const allDone = nextIdx === -1; const solvedCount = order.filter(g => solvedToday[g]).length; return (
{/* Progress bar */}
{solvedCount}/{order.length}
{order.map((game, idx) => { const solved = solvedToday[game]; const isCurrent = !allDone && idx === nextIdx; const isPending = !solved && !isCurrent; const { name, accent, subtitle, duration, symbol } = GAME_META[game]; return ( {/* Step number / status */}
{solved ? "✓" : isCurrent ? symbol : {idx + 1}} {/* Connector line below (not last) */} {idx < order.length - 1 && (
)}
{name} {!solved && isCurrent && ( {duration} )}

{solved && lastTimes[game] > 0 ? `Résolu en ${fmt(lastTimes[game])}` : isCurrent ? subtitle : ""}

{/* CTA */} {solved ? ( ) : isCurrent ? ( Jouer ) : ( )} ); })}
); } // ── All done banner ──────────────────────────────────────────────────────────── function AllDoneBanner({ nextPuzzleIn }: { nextPuzzleIn: string }) { return (
🎉

Bravo ! Tous les puzzles du jour complétés.

Prochain puzzle dans {nextPuzzleIn}

); } // ── Training row ────────────────────────────────────────────────────────────── function TrainingRow({ game, stats }: { game: GameId; stats: GameStats | undefined }) { const { name, accent } = GAME_META[game]; const pct = stats?.pct ?? 0; const label = !stats || stats.completed === 0 ? "Commencer" : `Niv. ${stats.nextLevel}`; return ( {name}
{stats?.completed ?? 0}/100 {label} ); } // ── Main page ───────────────────────────────────────────────────────────────── export default function Home() { const today = todayISO(); const [loaded, setLoaded] = useState(false); const [newUser, setNewUser] = useState(false); const [mode, setMode] = useState("free"); const [levelStats, setLevelStats] = useState | null>(null); const [solvedToday, setSolvedToday] = useState>({} as Record); const [lastTimes, setLastTimes] = useState>({} as Record); const [ritual, setRitual] = useState({ streak: 0, lastDate: "" }); const [showTraining, setShowTraining] = useState(false); const handleModeChange = (m: PlayMode) => { setMode(m); savePlayMode(m); }; useEffect(() => { const refresh = () => { setNewUser(isNewUser()); setMode(getPlayMode()); setLevelStats(allStats()); const solved = {} as Record; const times = {} as Record; for (const g of GAMES) { solved[g] = isDailySolved(g, today); const s = loadStats(g); times[g] = s.lastDate === today ? s.lastTime : 0; } setSolvedToday(solved); setLastTimes(times); const allSolved = GAMES.every(g => solved[g]); const r = allSolved ? updateRitualStreak(today) : getRitualStreak(); setRitual(r); setLoaded(true); }; refresh(); window.addEventListener("focus", refresh); document.addEventListener("visibilitychange", refresh); return () => { window.removeEventListener("focus", refresh); document.removeEventListener("visibilitychange", refresh); }; }, [today]); if (!loaded) { return (
{[...Array(5)].map((_, i) => (
))}
); } if (newUser) return ; const dateLabel = new Date(today + "T00:00:00").toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long", }); const sessionOrder = getSessionOrder(today); const totalSolvedToday = GAMES.filter(g => solvedToday[g]).length; const allSolvedToday = totalSolvedToday === GAMES.length; const now = new Date(); const midnight = new Date(now); midnight.setDate(midnight.getDate() + 1); midnight.setHours(0, 0, 0, 0); const hoursLeft = Math.ceil((midnight.getTime() - now.getTime()) / 3600000); const nextPuzzleIn = `${hoursLeft}h`; return (
{/* Header */}

Puzzle Trainer

{dateLabel}

Stats
{/* Streak */} {/* Daily puzzles */}

Puzzles du jour

{mode === "session" && (

Joue les 5 puzzles à la suite — l'ordre change chaque jour.

)} {allSolvedToday ? ( ) : mode === "session" ? ( ) : (
{GAMES.map(game => ( ))}
)}
{/* Training */}
{showTraining && (
{GAMES.map(game => ( ))}
)}
{/* Footer */}
Archives · Statistiques · Comment jouer
); }