Refonte visuelle : direction artistique Grèce antique
Remplace le thème générique (tokens navy/gold/ivory re-thématisables par édition) par une direction artistique fixe et codée en dur : tribunal égéen de nuit — marbre, or, mer sombre, sceaux de cire. Fondations (globals.css, layout.tsx) : - Polices next/font/google : Cinzel (titres, tracking large), Cormorant Garamond (citations italiques), Manrope (interface). - Nouveaux tokens couleur : ink/ink-2 (fond mer de nuit), marble/marble-2 (surfaces claires), gold/gold-bright (accents), oxblood (blâmes/erreurs), olive (honneurs/succès), sea, text-marble/text-mut (texte sur marbre — jamais de doré en texte courant, contraste insuffisant). - Classes utilitaires : .marble-surface (texture par dégradés, pas d'image de bruit), .meander-divider (frise à méandre grecque en SVG data-URI). Composants d'ornement (aucune image bitmap) : - components/laurel-wreath.tsx : couronne de laurier calculée par trigonométrie (deux arcs symétriques de feuilles), réutilisée en logo, favicon (icon.svg régénéré avec les mêmes coordonnées) et couronne du rang 1 du podium. - components/diamond-divider.tsx : séparateur ◆ avec filets dégradés. Header global (components/header.tsx, remplace nav-bar.tsx) : - Logo laurier + "LE TRIBUNAL" à gauche (cliquable vers le leaderboard). - Chip utilisateur à droite : avatar (bordure dorée), pseudo, badge de rôle (Archonte/Citoyen), points de gloire, menu déroulant (Mon profil, Le Conseil des Archontes si juge, Se déconnecter). - Onglets L'Agora / Le Crieur, frise à méandre en séparateur. - Responsive : nom du site et pseudo masqués sous le breakpoint sm, l'avatar reste toujours visible. Vocabulaire diégétique (toujours avec le terme fonctionnel à côté) : juges → Archontes, membres → Citoyens, leaderboard → L'Agora, journal → Le Crieur (décrets avec sceau de cire oxblood), points positifs/négatifs → Honneurs/Blâmes, points → gloires, section admin → Le Conseil des Archontes. Avatar : bordure dorée, couleur de fallback dérivée d'un hash du pseudo (au lieu d'une couleur fixe). Toutes les pages (login, signup, leaderboard/podium, journal, admin, profil) sont passées sur les nouveaux tokens/polices/vocabulaire. README.md et CLAUDE.md : section "Ré-thématisation" remplacée par "Direction artistique" documentant la palette, les polices et les ornements ; toute mention du système de re-thème par édition retirée. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { createClient } from "@/lib/supabase/client";
|
||||
import { LaurelWreath } from "@/components/laurel-wreath";
|
||||
import { Avatar } from "@/components/avatar";
|
||||
|
||||
function NavTab({ href, children }: { href: string; children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const isActive = pathname.startsWith(href);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className={`rounded-md px-3 py-1.5 font-heading text-xs tracking-wide uppercase transition-colors ${
|
||||
isActive ? "bg-gold/15 text-gold-bright" : "text-marble/80 hover:text-gold-bright"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function Header({
|
||||
pseudo,
|
||||
avatarUrl,
|
||||
isJudge,
|
||||
points,
|
||||
}: {
|
||||
pseudo: string;
|
||||
avatarUrl: string | null;
|
||||
isJudge: boolean;
|
||||
points: number;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [signingOut, setSigningOut] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||
setMenuOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
async function handleSignOut() {
|
||||
setSigningOut(true);
|
||||
const supabase = createClient();
|
||||
await supabase.auth.signOut();
|
||||
router.push("/login");
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-10 bg-ink-2/95 backdrop-blur">
|
||||
<div className="mx-auto flex max-w-4xl items-center justify-between px-4 py-3">
|
||||
<Link href="/leaderboard" className="flex items-center gap-2">
|
||||
<LaurelWreath className="h-7 w-7 shrink-0" color="#E7C560" />
|
||||
<span className="hidden font-heading text-sm tracking-[0.15em] text-gold-bright uppercase sm:inline">
|
||||
Le Tribunal
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<div ref={menuRef} className="relative">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMenuOpen((open) => !open)}
|
||||
aria-expanded={menuOpen}
|
||||
className="flex items-center gap-2 rounded-full px-1.5 py-1 transition-colors hover:bg-marble/10"
|
||||
>
|
||||
<Avatar pseudo={pseudo} avatarUrl={avatarUrl} size="sm" />
|
||||
<span className="hidden flex-col items-start leading-tight sm:flex">
|
||||
<span className="text-sm font-medium text-marble">{pseudo}</span>
|
||||
<span
|
||||
className={`font-heading text-[0.65rem] tracking-wide uppercase ${
|
||||
isJudge ? "text-gold-bright" : "text-text-mut"
|
||||
}`}
|
||||
>
|
||||
{isJudge ? "Archonte" : "Citoyen"} · {points} gloires
|
||||
</span>
|
||||
</span>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
className={`h-4 w-4 text-marble/60 transition-transform ${menuOpen ? "rotate-180" : ""}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path d="M6 9l6 6 6-6" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{menuOpen && (
|
||||
<div className="marble-surface absolute right-0 z-20 mt-2 w-52 rounded-lg border border-gold/40 py-1 shadow-lg">
|
||||
<Link
|
||||
href="/profile"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="block px-4 py-2 text-sm hover:bg-gold/10"
|
||||
>
|
||||
Mon profil
|
||||
</Link>
|
||||
{isJudge && (
|
||||
<Link
|
||||
href="/admin"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="block px-4 py-2 text-sm hover:bg-gold/10"
|
||||
>
|
||||
Le Conseil des Archontes
|
||||
</Link>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSignOut}
|
||||
disabled={signingOut}
|
||||
className="block w-full px-4 py-2 text-left text-sm text-oxblood hover:bg-gold/10 disabled:opacity-50"
|
||||
>
|
||||
{signingOut ? "…" : "Se déconnecter"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="mx-auto flex max-w-4xl items-center gap-1 px-4 pb-2">
|
||||
<NavTab href="/leaderboard">L'Agora</NavTab>
|
||||
<NavTab href="/journal">Le Crieur</NavTab>
|
||||
</nav>
|
||||
|
||||
<div className="meander-divider" />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user