Valentin ROBIN 3503dc2593
Build and deploy / deploy (push) Successful in 36s
Corrige les pièces qui débordaient du bac à droite/en bas
La bordure était posée directement sur le conteneur aux coordonnées
physiques (300x420) : à cause du box-sizing, la bordure mangeait sur
l'espace intérieur réel (292x416) sans que matter-js le sache, donc les
pièces pouvaient visuellement dépasser le cadre. La bordure vit
maintenant sur un conteneur externe non dimensionné, qui s'ajuste
automatiquement autour du conteneur physique sans réduire sa zone utile.

Ajoute aussi l'échelle de repère (ordre de fusion, taille proportionnelle
au rayon) et le second aperçu de la pièce suivante, travaillés en même
temps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 04:02:40 +02:00

Le Tribunal

Application web pour animer et scorer le jeu « Le Tribunal » entre amis pendant une semaine de vacances : comptes, rôles, leaderboard en direct, attribution de points par les juges et journal public des événements. Habillée sur le thème d'un tribunal de la Grèce antique (voir Direction artistique).


Fonctionnalités

  • 🔐 Création de compte par email + mot de passe (email confirmé, un compte par email)
  • 👤 Connexion / déconnexion, pseudo affiché partout (jamais l'email)
  • 🖼️ Profil éditable : pseudo modifiable une seule fois puis figé (sauf par un Archonte), photo recadrée en rond, redimensionnée et compressée côté client
  • 🏆 Leaderboard en direct (Supabase Realtime) : podium top 3 (or/argent/bronze, gestion des égalités), flèches ▲▼ de progression depuis le dernier « round »
  • ⚖️ Rôles : public / judge. Les Archontes (juges) attribuent des points (+/- avec montant personnalisé et motif, confirmation explicite avant envoi) et gèrent les Citoyens depuis Le Conseil des Archontes
  • 📣 Le crieur : journal public et en direct de toutes les attributions de points
  • 🎡 La Roulette : tirage au sort animé (avatars) parmi tous les membres ou les Citoyens uniquement
  • 🗓️ Le Calendrier des Dieux : agenda de la semaine sous le patronage du Panthéon, date du Tribunal avec compte à rebours, édition réservée aux Archontes
  • 📱 Responsive, pensé mobile-first et propre en vidéoprojection

Stack technique

Rôle Techno
Framework Next.js 16 (App Router) + TypeScript
Style Tailwind CSS v4 (tokens Grèce antique, voir globals.css)
Typographie Cinzel (titres), Cormorant Garamond (citations), Manrope (interface) — next/font/google
Auth · Base · Stockage · Temps réel Supabase (Auth, Postgres, Storage, Realtime)
Recadrage photo react-easy-crop + canvas (resize/compression client)
Email transactionnel SMTP custom (ex. Resend) branché sur Supabase Auth
Déploiement Docker Compose + Traefik (self-hosted) + Supabase (managé)

Prérequis

  • Node.js 20.19+ (Next.js 16 / React 19)
  • Un compte Supabase (offre gratuite suffisante)
  • Un fournisseur SMTP gratuit (ex. Resend, 100 emails/jour) — le service email intégré de Supabase a un rate limit trop bas pour un usage réel
  • Pour le déploiement (optionnel en local) : un serveur avec Docker + Traefik déjà en place, voir Déploiement

Installation

1. Cloner et installer

git clone ssh://git@git.alxczl.fr:7878/valentin/tribunal-app.git
cd tribunal-app
npm install

2. Créer le projet Supabase

  1. Créez un nouveau projet sur supabase.com.
  2. Dans SQL Editor, exécutez tout le contenu de supabase/schema.sql (tables profiles/points_log/days/events/settings, policies RLS, triggers, RPC, bucket avatars, Realtime). Le script est idempotent, vous pouvez le ré-exécuter sans risque après une mise à jour.
  3. Récupérez l'URL du projet et la clé anon dans Project Settings → API.
  4. Dans Authentication → Providers → Email, désactivez "Allow new users to sign up" — l'inscription publique est fermée, seules les invitations créent un compte (voir Authentification).
  5. Dans Authentication → URL Configuration, mettez Site URL sur http://localhost:3000/signup (puis votre domaine + /signup une fois déployé), et ajoutez la même valeur dans Redirect URLs. C'est la page où atterrit un lien d'invitation.
  6. Dans Authentication → Settings → SMTP Settings, branchez un fournisseur SMTP custom (ex. Resend) — le service email par défaut de Supabase est bridé à quelques emails/heure. Voir Email transactionnel ci-dessous.
  7. Invitez-vous vous-même depuis Authentication → Users → Invite user, finalisez votre compte via le lien reçu, puis désignez-vous Archonte (voir Rôles & administration).

3. Variables d'environnement

cp .env.local.example .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=votre_cle_anon

⚠️ Ne mettez jamais la clé service_role dans le front ni dans le dépôt.

4. Lancer en local

npm run dev

L'application tourne sur http://localhost:3000.


Scripts

Commande Description
npm run dev Serveur de développement
npm run build Build de production
npm run start Lance le build de production
npm run lint Vérification du code (ESLint)

Déploiement (Docker + Traefik)

Self-hosted, pas de Vercel. Un serveur avec Docker Compose et un reverse proxy Traefik déjà en place (réseau externe partagé, voir TRAEFIK_NETWORK), plus un pipeline Gitea Actions qui build et déploie automatiquement à chaque push sur la branche principale.

  1. Sur le serveur, copiez .env.docker.example en .env.docker et renseignez :
    • APP_DOMAIN : le domaine sur lequel l'app doit répondre (utilisé dans le label Traefik Host(\${APP_DOMAIN}`)`).
    • TRAEFIK_NETWORK : le nom du réseau Docker externe sur lequel Traefik écoute.
    • WEBHOOK_SECRET : secret partagé avec le workflow Gitea Actions (openssl rand -hex 16).
  2. docker compose up -d démarre le conteneur app (Next.js standalone) et le petit serveur webhook (docker/webhook/receive.py) qui reçoit les déploiements.
  3. Le workflow .gitea/workflows/deploy.yml build l'app à chaque push sur main et POST le build vers le webhook, qui déclenche un hot-swap (voir docker/entrypoint.sh) sans downtime — pas de rebuild manuel nécessaire une fois configuré.
  4. Changer de domaine : mettez à jour APP_DOMAIN dans .env.docker sur le serveur, puis recréez le conteneur (docker compose up -d --force-recreate app) pour que Traefik prenne en compte le nouveau label Host(). Pensez aussi à mettre à jour, côté Supabase Dashboard, Authentication → URL Configuration (Site URL + Redirect URLs) et le template d'email d'invitation (logo en dur), sans quoi les liens d'invitation et le logo pointeront encore vers l'ancien domaine.

Structure du projet

tribunal-app/
├─ src/
│  ├─ app/
│  │  ├─ login/page.tsx              # Connexion (email + mot de passe)
│  │  ├─ signup/page.tsx             # Finalisation d'un compte invité (mot de passe, pseudo, photo)
│  │  ├─ leaderboard/
│  │  │  ├─ page.tsx                 # Fetch initial + rôle de l'utilisateur
│  │  │  └─ leaderboard-view.tsx     # Realtime, podium, liste, contrôles juges
│  │  ├─ journal/                    # « Le Crieur » : fil des décrets en direct
│  │  ├─ roulette/                   # « La Roulette » : tirage au sort animé
│  │  ├─ calendrier/                 # « Le Calendrier des Dieux » : agenda, panthéon, date du Tribunal
│  │  ├─ admin/                      # Le Conseil des Archontes : gestion des membres, reset du repère
│  │  ├─ profile/                    # Édition pseudo (verrou) / photo, déconnexion
│  │  ├─ layout.tsx                  # Layout + header global
│  │  ├─ icon.svg                    # Favicon (couronne de laurier)
│  │  └─ page.tsx                    # Redirection selon l'état de connexion
│  ├─ components/                    # Header (logo + chip utilisateur + menu), Avatar, AvatarPicker,
│  │  │                              # Podium, JudgePointControls, PointsConfirmControls, icons,
│  │  │                              # LaurelWreath, DiamondDivider
│  ├─ lib/
│  │  ├─ ranking.ts                  # Classement avec égalités + calcul des flèches
│  │  ├─ pantheon.ts                 # Préréglages des dieux + icônes par type d'événement
│  │  ├─ image.ts                    # Recadrage/compression canvas côté client
│  │  └─ supabase/                   # Clients Supabase (browser / server / proxy)
│  └─ proxy.ts                       # Protection des routes + rafraîchissement session
├─ supabase/schema.sql               # Schéma complet + policies RLS + triggers + RPC (versionné)
├─ .env.local.example
└─ README.md

Authentification

Sur invitation uniquement : l'auto-inscription publique est désactivée côté Supabase (Authentication → Providers → Email → "Allow new users to sign up" décoché). Il n'y a plus de bouton « Créer un compte » sur /login.

Pour ajouter un membre : Authentication → Users → Invite user (email). Supabase lui envoie un email (via le SMTP configuré) contenant un lien d'invitation qui, une fois cliqué, établit directement une session sur /signup — c'est cette page qui sert alors à finaliser le compte (choix du mot de passe, du pseudo, upload de la photo). Sans session active (lien absent, invalide ou expiré), /signup affiche un message « invitation requise » plutôt qu'un formulaire d'inscription.

Le profil (public.profiles) est créé automatiquement dès l'invitation via un trigger sur auth.users (pseudo temporaire "Nouveau membre" tant que la finalisation n'a pas eu lieu). Le pseudo est purement un nom d'affichage (leaderboard, profil, journal) : l'email n'est jamais montré aux autres membres.

Verrou de pseudo

Chaque membre peut changer son pseudo une seule fois depuis /profile. Dès l'enregistrement, pseudo_locked passe à true et le champ devient en lecture seule (« pseudo figé — contacte un Archonte pour le changer »). Un Archonte peut toujours modifier le pseudo de quiconque et, s'il le souhaite, redéverrouiller un compte depuis Le Conseil des Archontes.

Rôles & administration

Deux rôles : public (défaut) et judge. Les 2 premiers juges sont désignés manuellement via le SQL Editor :

select id, pseudo from public.profiles;
update public.profiles set role = 'judge' where id = '<uuid-du-membre>';

Ensuite, un Archonte peut promouvoir/rétrograder d'autres membres et éditer leur pseudo depuis Le Conseil des Archontes (visible uniquement par les Archontes, et vérifié côté serveur — pas seulement caché en front).

Email transactionnel

Le service email intégré de Supabase (sans SMTP custom) limite l'envoi à quelques emails par heure — largement insuffisant dès qu'on teste à plusieurs. Configurez un SMTP custom dans Authentication → Settings → SMTP Settings, par exemple avec Resend (gratuit, 100 emails/jour) :

Champ Valeur
Sender email onboarding@resend.dev (domaine de test Resend) ou une adresse d'un domaine que vous avez vérifié
Host smtp.resend.com
Port 465
Username resend (en minuscules)
Password votre clé API Resend

Avec le domaine de test onboarding@resend.dev, Resend ne livre qu'à l'adresse email du compte Resend lui-même. Pour envoyer à tous vos amis, vérifiez votre propre domaine dans Resend.


Points, journal et classement

  • Attribution de points : depuis L'Agora, un Archonte clique +/ (montant personnalisable, motif optionnel) — le delta reste en attente (affiché en couleur) jusqu'à un clic sur Confirmer (envoie la RPC award_points) ou Annuler. Le client n'écrit jamais directement la colonne points : la RPC vérifie le rôle juge côté serveur, applique le delta (négatif autorisé, pas de plancher à 0) et journalise l'opération.
  • Le crieur (/journal) : fil de tous les événements (« Untel a donné +5 à Untel — motif »), visible par tous les authentifiés, alimenté par Supabase Realtime.
  • Podium : top 3 mis en avant (or/argent/bronze), gère proprement les égalités (rangs partagés) et les groupes de moins de 3 membres.
  • Flèches de progression : ▲/▼/= comparent le rang actuel au dernier repère. Un Archonte fige un nouveau repère via Nouveau round dans Le Conseil des Archontes.

Le Calendrier des Dieux

Agenda de la semaine (/calendrier) : chaque journée est placée sous le patronage d'une divinité (nom, domaine, emblème SVG, couleur d'accent — 8 préréglages fournis dans lib/pantheon.ts : Dionysos, Arès, Athéna, Aphrodite, Hermès, Poséidon, Hadès, Zeus ; un Archonte peut en saisir d'autres librement). Chaque journée liste ses événements (activité/défi/épreuve/tribunal, heure, lieu, description).

  • Lecture : tous les authentifiés.
  • Écriture (créer/modifier/supprimer une journée ou un événement, définir la date du Tribunal) : réservée aux Archontes, imposée par policies RLS sur days/events/settings — jamais par un simple masquage des boutons côté client.
  • Bannière : compte à rebours avant la date du Tribunal (settings.tribunal_date), éditable par les Archontes.
  • Le jour courant est mis en valeur, les jours passés sont estompés, le jour contenant un événement de type tribunal reçoit un accent oxblood distinct.
  • Realtime activé sur les trois tables : toute modification est visible immédiatement par tous.

Sécurité

  • Mots de passe hachés et gérés par Supabase Auth (jamais stockés en clair).
  • Row Level Security activée sur profiles et points_log. La RLS étant au niveau ligne, les colonnes sensibles (role, points, pseudo_locked, pseudo figé) sont verrouillées par un trigger BEFORE UPDATE qui ne fait jamais confiance à ce qu'envoie le client.
  • Les points ne sont modifiables que via la RPC award_points (SECURITY DEFINER), jamais par une écriture directe — même un Archonte ne peut pas modifier la colonne points à la main.
  • points_log est en lecture seule pour les clients ; seule la RPC peut y écrire.
  • La page /admin vérifie le rôle côté serveur (Server Component) avant de rendre quoi que ce soit — pas de simple masquage front.
  • days/events/settings (Calendrier) : lecture ouverte à tous les authentifiés, écriture (insert/update/delete) réservée au rôle judge par policies RLS dédiées — un Citoyen qui appellerait l'API directement se ferait rejeter, pas seulement masquer les boutons.

Direction artistique

Thème fixe : un tribunal égéen de nuit — marbre, or, mer sombre, sceaux de cire. Tokens couleur (globals.css) :

Token Valeur Usage
--ink / --ink-2 #0A1B33 / #0F2748 Fond de l'app (mer de nuit), header, boutons primaires
--marble / --marble-2 #F4ECD8 / #E9DEC2 Surfaces claires (cartes, formulaires)
--gold / --gold-bright #C9A227 / #E7C560 Accents, bordures, 1ʳᵉ place
--oxblood #A5342A Blâmes (points négatifs), sceaux, erreurs
--olive #5E6B3B Honneurs (points positifs), succès
--sea #2E6E7E Accent secondaire
--silver / --bronze #C7CDD6 / #B08D57 Accents 2ᵉ/3ᵉ place du podium
--text-marble / --text-mut #2A2116 / #7A6A4C Texte sur surfaces claires (jamais de doré sur marbre pour du texte courant — contraste insuffisant)

Ornements en SVG/CSS inline (pas d'images bitmap) : couronne de laurier (components/laurel-wreath.tsx, calculée par trigonométrie), frise à méandre (.meander-divider), séparateur losange (components/diamond-divider.tsx), texture marbre (.marble-surface).

Vocabulaire (toujours accompagné du terme fonctionnel) : juges → Archontes, membres → Citoyens, leaderboard → L'Agora, journal → Le Crieur, points positifs/négatifs → Honneurs/Blâmes, section admin → Le Conseil des Archontes.

Ce thème est codé en dur (pas de système de re-thématisation par édition) — changer l'apparence pour une prochaine édition demande d'éditer directement les tokens et le vocabulaire.


Roadmap

  • Éditions — table editions (année, mood board) pour rattacher les points à une édition donnée
  • Le jeu du Tribunal — le Char, batailles de cul sec, ostracisme, timer du Gardien (la Roulette est livrée)
  • Mode grand écran — vue leaderboard optimisée vidéoprojecteur

Licence

Projet personnel et privé, destiné à un usage entre amis.

S
Description
Application web pour animer et scorer le jeu « Le Tribunal » entre amis : comptes, profils et leaderboard.
Readme 2.1 MiB
Languages
TypeScript 76.3%
PLpgSQL 21.5%
CSS 1.8%
Python 0.2%
JavaScript 0.1%