feat: add about page, global stats counter, and blitz screen updates

- Create about page with game modes, technologies, and paliers
- Add stats API endpoint for global game counter
- Update home screen with emoji-based tier system (🌱-🚀)
- Rewrite BlitzScreen to match new puzzle-based win/lose phases
- Add simple-icons package for tech stack icons
- Update metadata with wikirush.xyz domain
- Fix em dashes to hyphens throughout codebase
- Update footer with privacy and about links
This commit is contained in:
jessy-david-dev
2026-04-10 22:26:24 +02:00
parent dc12abf796
commit 47b8de0b67
17 changed files with 487 additions and 2989 deletions
+83 -25
View File
@@ -25,7 +25,7 @@ export function BlitzScreen({ onBack }: { onBack: () => void }) {
<div className="text-5xl"></div>
<h2 className="text-2xl sm:text-3xl font-black">Mode Blitz</h2>
<p className="text-[#888] text-sm sm:text-base leading-relaxed">
Tu as <span className="text-[#f0f0f0] font-bold">2 minutes</span> pour visiter un maximum d&apos;articles Wikipedia différents en cliquant sur les liens. Ton score = nombre d&apos;articles uniques visités.
Tu as <span className="text-[#f0f0f0] font-bold">2 minutes</span>{" "}pour naviguer de l&apos;article de départ jusqu&apos;à l&apos;article cible en cliquant sur les liens Wikipedia. Plus tu es rapide, mieux c&apos;est !
</p>
<button className={btnPrimary} onClick={game.start} disabled={game.loading}>
{game.loading ? "Préparation..." : "Lancer le chrono !"}
@@ -33,26 +33,37 @@ export function BlitzScreen({ onBack }: { onBack: () => void }) {
</div>
);
if (game.phase === "ended") return (
if (game.phase === "won") return (
<div className="min-h-dvh bg-[#0f0f0f] text-[#f0f0f0] animate-fade-in flex items-center justify-center px-4 py-8">
<div className="w-full max-w-sm sm:max-w-md text-center flex flex-col gap-5">
<div className="text-5xl sm:text-6xl leading-none"></div>
<h2 className="text-2xl sm:text-3xl font-black">Temps écoulé !</h2>
<div className="text-5xl sm:text-6xl leading-none">🎉</div>
<h2 className="text-2xl sm:text-3xl font-black">Article trouvé !</h2>
{game.puzzle && (
<p className="text-[#888] text-sm">
<span className="text-[#f0f0f0] font-semibold">{game.puzzle.start}</span>
{" → "}
<span className="text-[#7c3aed] font-semibold">{game.puzzle.target}</span>
</p>
)}
<div className="flex gap-8 justify-center">
<div className="flex flex-col gap-1">
<span className="text-4xl font-black text-[#7c3aed]">{game.visited.length}</span>
<span className="text-xs text-[#888]">articles visités</span>
<span className="text-4xl font-black text-[#7c3aed]">{fmtLeft(game.timeLeft)}</span>
<span className="text-xs text-[#888]">temps restant</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-4xl font-black text-[#7c3aed]">{game.clicks}</span>
<span className="text-xs text-[#888]">clics</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-4xl font-black text-[#7c3aed]">{game.history.length}</span>
<span className="text-xs text-[#888]">articles</span>
</div>
</div>
<div className="bg-[#1a1a1a] rounded-xl px-4 py-3 flex flex-wrap gap-1 text-xs text-[#888] text-left max-h-48 overflow-y-auto">
{game.visited.map((t, i) => (
{game.history.map((t, i) => (
<span key={i} className="flex items-center gap-1">
{i > 0 && <span className="text-[#555]"></span>}
<span>{t}</span>
<span className={t === game.puzzle?.target ? "text-[#7c3aed] font-bold" : ""}>{t}</span>
</span>
))}
</div>
@@ -60,7 +71,48 @@ export function BlitzScreen({ onBack }: { onBack: () => void }) {
<button className={btnPrimary} onClick={game.start} disabled={game.loading}>
Rejouer
</button>
<button className={btnGhost} onClick={onBack}>Accueil</button>
<button className={btnGhost} onClick={() => { game.reset(); onBack(); }}>Accueil</button>
</div>
</div>
</div>
);
if (game.phase === "lost") return (
<div className="min-h-dvh bg-[#0f0f0f] text-[#f0f0f0] animate-fade-in flex items-center justify-center px-4 py-8">
<div className="w-full max-w-sm sm:max-w-md text-center flex flex-col gap-5">
<div className="text-5xl sm:text-6xl leading-none"></div>
<h2 className="text-2xl sm:text-3xl font-black">Temps écoulé !</h2>
{game.puzzle && (
<p className="text-[#888] text-sm">
L&apos;objectif était d&apos;atteindre{" "}
<span className="text-[#7c3aed] font-semibold">{game.puzzle.target}</span>
</p>
)}
<div className="flex gap-8 justify-center">
<div className="flex flex-col gap-1">
<span className="text-4xl font-black text-red-400">{game.clicks}</span>
<span className="text-xs text-[#888]">clics</span>
</div>
<div className="flex flex-col gap-1">
<span className="text-4xl font-black text-red-400">{game.history.length}</span>
<span className="text-xs text-[#888]">articles visités</span>
</div>
</div>
{game.history.length > 0 && (
<div className="bg-[#1a1a1a] rounded-xl px-4 py-3 flex flex-wrap gap-1 text-xs text-[#888] text-left max-h-48 overflow-y-auto">
{game.history.map((t, i) => (
<span key={i} className="flex items-center gap-1">
{i > 0 && <span className="text-[#555]"></span>}
<span>{t}</span>
</span>
))}
</div>
)}
<div className="flex flex-col gap-2.5">
<button className={btnPrimary} onClick={game.start} disabled={game.loading}>
Réessayer
</button>
<button className={btnGhost} onClick={() => { game.reset(); onBack(); }}>Accueil</button>
</div>
</div>
</div>
@@ -90,26 +142,32 @@ export function BlitzScreen({ onBack }: { onBack: () => void }) {
</div>
{/* Bottom bar */}
<div className="fixed bottom-0 left-0 right-0 z-50 bg-[#0f0f0f]/97 backdrop-blur-sm border-t border-[#2e2e2e] flex items-center justify-between px-3 sm:px-4 py-2 gap-3">
<div className="flex items-center gap-3">
<div className="flex flex-col items-center gap-0.5">
<span className="text-[8px] font-bold uppercase tracking-wider text-[#888]"> Articles</span>
<span className="text-sm font-black text-[#7c3aed] tabular-nums">{game.visited.length}</span>
<div className="fixed bottom-0 left-0 right-0 z-50 bg-[#0f0f0f]/97 backdrop-blur-sm border-t border-[#2e2e2e] px-3 sm:px-4 py-2 flex flex-col gap-1">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3">
<div className="flex flex-col items-center gap-0.5">
<span className="text-[8px] font-bold uppercase tracking-wider text-[#888]">Clics</span>
<span className="text-sm font-black tabular-nums">{game.clicks}</span>
</div>
</div>
<div className="flex flex-col items-center gap-0.5">
<span className="text-[8px] font-bold uppercase tracking-wider text-[#888]">Clics</span>
<span className="text-sm font-black tabular-nums">{game.clicks}</span>
{/* Timer */}
<div className={`text-2xl sm:text-3xl font-black tabular-nums transition-colors ${danger ? "text-red-400 animate-pulse" : "text-[#f0f0f0]"}`}>
{fmtLeft(game.timeLeft)}
</div>
</div>
{/* Timer */}
<div className={`text-2xl sm:text-3xl font-black tabular-nums transition-colors ${danger ? "text-red-400" : "text-[#f0f0f0]"}`}>
{fmtLeft(game.timeLeft)}
</div>
<div className="text-xs text-[#888] text-right max-w-28 truncate">
{game.visited.length > 0 ? game.visited[game.visited.length - 1] : ""}
{game.puzzle && (
<div className="text-right">
<div className="text-[8px] font-bold uppercase tracking-wider text-[#888]">Cible</div>
<div className="text-xs font-bold text-[#7c3aed] max-w-28 truncate">{game.puzzle.target}</div>
</div>
)}
</div>
{game.history.length > 0 && (
<div className="text-[10px] text-[#555] truncate">
{game.history.join(" ")}
</div>
)}
</div>
</div>
);