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>
10 KiB
@AGENTS.md
Le Tribunal
Application web pour un groupe d'amis (~12 personnes) qui anime un jeu et des olympiades pendant une semaine de vacances : comptes, rôles, leaderboard en direct, attribution de points par des juges, journal public des événements. Habillée sur le thème d'un tribunal de la Grèce antique (voir section 6).
1. État actuel
V1 à V4 sont livrées :
- V1 : comptes, profils, leaderboard statique.
- V2 : authentification par email réel (plus de pseudo+email interne), rôles
public/judge, verrou de pseudo (modifiable une fois), section Admin. - V3 : attribution de points par les juges (RPC sécurisée, +/- avec confirmation explicite), journal public « le crieur », podium top 3, flèches de progression, recadrage/compression photo côté client.
- V4 : direction artistique Grèce antique (marbre/or/mer de nuit, polices Cinzel/Cormorant Garamond/Manrope, vocabulaire Archontes/Citoyens/Agora/Crieur), header global avec chip utilisateur.
Hors périmètre pour l'instant (voir roadmap en fin de doc) : éditions/saisons, le jeu du Tribunal lui-même (Char, roulette, ostracisme, timer du Gardien), mode grand écran.
2. Stack technique
- Framework : Next.js 16 (App Router) + TypeScript
- Style : Tailwind CSS v4 (design tokens en variables CSS, voir
globals.css) - Auth + Base + Stockage + Temps réel : Supabase (Auth, Postgres, Storage, Realtime)
- Recadrage photo :
react-easy-crop+ canvas natif (resize/compression client, jamais d'upload de l'image brute) - Déploiement : Vercel (front) + Supabase (managé) — pas encore déployé, tourne en local pour l'instant
Next.js 16 a renommé middleware.ts en proxy.ts (fichier src/proxy.ts, fonction proxy). src/lib/supabase/middleware.ts est un nom de fichier utilitaire interne, sans rapport avec cette convention.
3. Modèle de données (supabase/schema.sql, source de vérité)
Le script est idempotent : toujours le ré-exécuter en entier après une modification, jamais de migration incrémentale séparée.
public.profiles (liée à auth.users, créée automatiquement par un trigger sur auth.users)
| Colonne | Notes |
|---|---|
id |
= auth.users.id |
pseudo |
affiché partout, unique (insensible à la casse) |
avatar_url |
URL publique Storage ({user_id}.webp) |
points |
jamais modifiable directement — uniquement via RPC award_points |
role |
public (défaut) ou judge |
pseudo_locked |
passe à true automatiquement au 1er changement de pseudo |
previous_rank |
repère de classement, mis à jour par reset_rank_reference() |
public.points_log (journal, lecture seule côté client)
id, target_id, judge_id, delta, reason, created_at — écrit uniquement par la RPC award_points.
Colonnes sensibles : RLS + trigger, jamais confiance au client
La RLS est au niveau ligne : elle ne peut pas exprimer « cette colonne seulement si tel rôle ». Le verrouillage fin des colonnes (role, points, pseudo_locked, pseudo figé, previous_rank) passe par le trigger enforce_profile_update() :
points: bloqué pour tout le monde, y compris les juges, en écriture directe. Seule la RPCaward_points()(SECURITY DEFINER) peut la modifier, via un flag de session (app.bypass_points_lock) qui autorise explicitement CETTE écriture précise.role,previous_rank: modifiables uniquement par unjudge.pseudo: modifiable une fois par le propriétaire (auto-lock ensuite), ou à tout moment par unjudge.auth.uid() is null(SQL Editor, migrations) bypass le trigger : ces accès sont déjà fiables par construction.
Ce pattern (RLS pour l'accès à la ligne + trigger BEFORE UPDATE pour l'accès aux colonnes) est la convention du projet — le reproduire pour toute nouvelle colonne sensible plutôt que d'inventer autre chose.
RPC exposées
is_pseudo_taken(p_pseudo)— anon + authenticated, ne renvoie qu'un booléen (l'anon ne peut pas lireprofiles).award_points(p_target_id, p_delta, p_reason)— authenticated, vérifierole = 'judge'côté serveur, jamais côté client.reset_rank_reference()— authenticated, vérifierole = 'judge', fige le classement courant dansprevious_rank.
4. Authentification
Email + mot de passe (Supabase Auth impose l'unicité de l'email). Confirmation d'email activée — nécessite un SMTP custom configuré côté Supabase (le service intégré est trop limité en volume, voir README). Le pseudo est un simple nom d'affichage stocké dans profiles, jamais l'email n'est montré aux autres membres.
5. Pages & navigation
| Route | Contenu |
|---|---|
/ |
Redirige vers /leaderboard si connecté, sinon /login |
/login |
Email + mot de passe |
/signup |
Email + mot de passe + pseudo + photo (recadrée) ; gère l'attente de confirmation email |
/leaderboard |
« L'Agora » — podium top 3 + liste, Realtime, contrôles de points pour les juges |
/journal |
« Le Crieur » — fil live des décrets (attributions de points), lecture pour tous |
/profile |
Pseudo (verrouillable), photo, déconnexion |
/admin |
« Le Conseil des Archontes » — juges uniquement, vérifié côté serveur (Server Component) — gestion des membres, reset du repère de classement |
/leaderboard, /profile, /admin, /journal sont protégées par src/proxy.ts (redirection /login si non connecté). L'accès juge-only de /admin n'est pas géré par le proxy (il n'a pas facilement le rôle) — c'est la page elle-même qui vérifie et redirige.
Le header global (components/header.tsx) est rendu dans layout.tsx pour tout utilisateur connecté : logo (couronne de laurier + « Le Tribunal ») à gauche, chip utilisateur (avatar, pseudo, rôle, points) avec menu déroulant à droite, onglets L'Agora/Le Crieur en dessous.
6. Look & feel — Grèce antique
Thème fixe et codé en dur (pas de système de re-thématisation par édition — voir roadmap). Un tribunal égéen de nuit : marbre, or, mer sombre, sceaux de cire.
Tokens couleur (globals.css, exposés comme utilities Tailwind via @theme inline) :
--ink #0A1B33/--ink-2 #0F2748: fond de l'app (dégradés radiaux mer de nuit), header, boutons primaires.--marble #F4ECD8/--marble-2 #E9DEC2: surfaces claires (.marble-surface, classe utilitaire avec léger dégradé/grain).--gold #C9A227/--gold-bright #E7C560: accents, bordures, 1ʳᵉ place. Jamais pour du texte courant surmarble(contraste insuffisant) — utiliser--text-marbleà la place.--oxblood #A5342A: blâmes (points négatifs), sceaux, erreurs.--olive #5E6B3B: honneurs (points positifs), succès.--sea #2E6E7E: accent secondaire.--text-marble #2A2116/--text-mut #7A6A4C: texte sur surfaces claires.
Polices (next/font/google, variables CSS, exposées comme font-heading/font-serif/font-sans) : Cinzel (titres, noms, chiffres de score — majuscules, tracking-[0.1em]), Cormorant Garamond (citations/sous-titres, italique), Manrope (interface, corps de texte).
Ornements en SVG/CSS inline (jamais d'image bitmap) :
components/laurel-wreath.tsx— couronne de laurier calculée par trigonométrie (deux arcs symétriques de feuilles), utilisée en logo, sur le rang 1 du podium, etc. Le favicon (src/app/icon.svg) est une version statique des mêmes coordonnées..meander-divider(classe globale) — frise à méandre grecque enbackground-imageSVG data-URI, séparateur discret.components/diamond-divider.tsx— séparateur ◆ avec filets dégradés..marble-surface(classe globale) — texture marbre via dégradés (pas d'image de bruit).
Convention de carte : marble-surface rounded-2xl border border-gold/40 shadow-xl (formulaires, podium) ou rounded-lg border-gold/25 pour les lignes de liste. Bouton primaire : bg-ink-2 text-gold-bright font-heading uppercase hover:bg-ink. Bouton secondaire : border border-gold/30 text-text-marble hover:bg-gold/10.
Vocabulaire diégétique (toujours accompagné du terme fonctionnel dans le code/les commentaires, pour que ça reste maintenable) : juges/admins → Archontes, membres → Citoyens, leaderboard → L'Agora, journal des points → Le Crieur (décrets), points positifs/négatifs → Honneurs/Blâmes, points → gloires, section admin → Le Conseil des Archontes.
7. Conventions
- Toujours passer par les policies RLS + trigger ; ne jamais exposer la
service_role keycôté client. - Toute nouvelle colonne sensible sur
profiles(ou une future table) suit le pattern RLS+trigger de la section 3, pas ungrant/revokepar colonne (qui ne peut pas distinguer les rôles applicatifs). - Aperçus locaux d'image avant upload (crop, fichier choisi) =
blob:/data:URL → toujoursunoptimizedsurnext/imageou une balise<img>classique (next/imagene sait pas résoudre ces schémas côté serveur). - Commandes :
npm run dev,npm run build,npm run lint. - Le schéma SQL (
supabase/schema.sql) est la seule source de vérité pour la base — le tenir à jour à chaque changement de données/sécurité, et le garder idempotent.
8. Roadmap (pas encore codé)
- Éditions : table
editions(année, mood board) ; les points deviendront rattachables à une édition donnée. Le thème visuel (Grèce antique) reste fixe, ce n'est pas un système de re-thématisation par édition. - Le jeu du Tribunal : le Char (zone de pénalité), batailles de cul sec, ostracisme, roulette, timer du Gardien.
- Mode grand écran : vue leaderboard optimisée pour vidéoprojecteur.
- Déploiement : pas encore fait (Vercel + Supabase managé prévus, voir README).