/* ═══════════════════════════════════════════════════════════════════════════
   PETANQUE BELGE (Pétanque Belge) — feuille de style — direction ÉMAIL & ACIER

   Quatre matières, pas une de plus :
     l'émail   — l'aplat marine, dur, sans dégradé          --panneau
     le papier — l'ivoire et le blanc, avec grain           --sol / --carte
     l'or      — l'accent, 3 à 5 % de la surface            --jaune
     l'acier   — la boule : une RECETTE, pas une couleur    --acier

   Deux lieux, pas deux réglages :
     Plein Cagnard   data-theme="cagnard"   (alias historique : "clair")
     La Buvette      data-theme="buvette"   (alias historique : "sombre")
     Sans attribut, on suit le réglage du téléphone.

   RÈGLE DE TENUE DU FICHIER — celle qui a coûté 25 textes invisibles :
   aucune valeur de couleur n'est écrite deux fois. Les hexadécimaux ne vivent
   QUE dans les rampes du § 1 ; tout le reste n'est que des var(). Et un jeton
   sémantique s'écrit light-dark(clair, sombre) : la syntaxe EXIGE les deux
   arguments, il devient donc impossible d'oublier un thème.

   Découpage de propriété : § 1 à 8 = les jetons + @layer socle (agent S1) ;
   @layer composants / pages / utilitaires (agent S2).
   ═══════════════════════════════════════════════════════════════════════════ */

@layer socle, composants, pages, utilitaires;

/* ══════════════════════════════════════════════════════ 1. LES PRIMITIVES ══
   Trois teintes relevées sur la bouteille, gelées : seules L et C varient le
   long des rampes. C'est ce qui rend le contraste calculable au lieu d'être
   testé — à L égale, le poids perçu est égal. Les hex SONT la conversion
   exacte des oklch en commentaire ; elle est faite une fois, pas par le
   navigateur, pour ne pas payer une seconde déclaration par jeton.
   AUCUNE de ces valeurs n'est nouvelle : ce sont les cinq couleurs de la
   bouteille, complétées vers le haut et vers le bas. */

:root {

  /* — or, h 81,4 : le verre. Ancre 500 = la marque, inchangée. */
  --or-50:   #FEF6E7;   /* oklch(97.5% .022 81.4) */
  --or-100:  #FFEAC6;   /* oklch(94.5% .052 81.4)  survol de ligne, voile */
  --or-200:  #FFD893;   /* oklch(90.0% .097 81.4) */
  --or-300:  #FFC75C;   /* oklch(86.0% .138 81.4)  l'or ÉCRIVABLE sur marine */
  --or-400:  #FCBD3A;   /* oklch(83.5% .155 81.4)  anneau de focus en nuit */
  --or-500:  #F5B41C;   /* LA MARQUE — relevée sur la bouteille, intouchable */
  --or-600:  #DB9F04;   /* oklch(74.0% .152 81.4) */
  --or-700:  #C48E09;   /* oklch(68.2% .139 81.4) */
  --or-800:  #876104;   /* LE BRONZE — 5,61:1 sur blanc, 5,08:1 sur ivoire.
                           Le SEUL or qui a le droit d'être une lettre sur
                           fond clair. C'est lui qui répare le rang de podium
                           (2,91:1 avec l'ancien #C88C08) et a:hover. */
  --or-900:  #725202;   /* oklch(46.0% .094 81.4) */
  --or-950:  #4A3300;   /* oklch(34.0% .070 81.4)  encre sur aplat or */

  /* — marine, h 270,5 : le cartouche. 850/875/925 = l'élévation nocturne. */
  --marine-50:   #F1F3FA;
  --marine-100:  #E3E8F4;   /* texte sur panneau */
  --marine-200:  #CCD4E9;   /* texte secondaire sur panneau */
  --marine-300:  #ACB7D5;
  --marine-400:  #8491B7;
  --marine-500:  #65729E;   /* bordure porteuse en nuit — 3,2:1 sur la carte */
  --marine-600:  #445282;
  --marine-700:  #2C3969;
  --marine-750:  #24305E;
  --marine-800:  #1A2452;   /* LE PANNEAU — l'émail */
  --marine-850:  #16204A;   /* la carte de nuit */
  --marine-875:  #131C40;   /* l'élévation de nuit */
  --marine-900:  #101838;   /* la nuit */
  --marine-925:  #101734;
  --marine-950:  #0A1028;   /* L'ABYSSE */
  --marine-975:  #05081B;

  /* Hors rampe, et assumé : l'accent marine lisible en nuit. C'est la valeur
     du correctif d'urgence du 01/09 (25 vues écrivent color:var(--bleu) en
     style en ligne, donc sans surcharge possible) — 8,55:1 sur --marine-850.
     On la conserve au hexadécimal près pour ne rien changer à ce qui est en
     production ; sa place sur la rampe serait entre 200 et 300. */
  --marine-accent: #A9B8F2;

  /* — terre : surfaces chaudes en haut (h 87), encres froides en bas (h 265). */
  --terre-50:   #FAF3E3;   /* L'IVOIRE — l'étiquette */
  --terre-100:  #F4EAD4;
  --terre-200:  #E5DAC0;
  --terre-300:  #D5C7A6;
  --terre-400:  #B2AA97;
  --terre-500:  #858896;   /* 3,52:1 sur blanc — bordure porteuse */
  --terre-600:  #5B6375;   /* 5,45:1 sur l'ivoire — texte secondaire */
  --terre-700:  #3F475B;
  --terre-800:  #282F45;
  --terre-900:  #161D33;

  /* — les sens : jamais mélangés à la marque.
       500 = l'APLAT (stable dans les deux thèmes, le blanc écrit dessus)
       300 = l'ENCRE de nuit · 50 = le voile de jour · 950 = le voile de nuit */
  --vert-500:  #026940;  --vert-300:  #6CD79D;
  --vert-50:   #DDF3E5;  --vert-950:  #112A1C;
  --rouge-500: #A82418;  --rouge-300: #F68675;
  --rouge-50:  #FDE3DE;  --rouge-950: #3C1A15;
  --ambre-500: #825604;  --ambre-300: #F7B83D;
  --ambre-50:  #FBE9CA;  --ambre-950: #2F2107;

  --blanc: #FFFFFF;
}

/* ══════════════════════════════════════════ 2. LES RÔLES — PALETTE CLAIRE ══
   ATTENTION, PIÈGE DOCUMENTÉ : « --x: light-dark(a, b) » écrit hors @supports
   n'est PAS rejeté à l'analyse — il est stocké tel quel puis devient invalide
   à l'usage, et « background: var(--x) » tombe alors à transparent. La palette
   claire est donc déclarée ICI, AVANT le bloc @supports, et sert de repli
   universel : un navigateur sans light-dark() affiche Plein Cagnard. */

:root {
  color-scheme: light;

  /* surfaces — le papier */
  --sol:     var(--terre-50);
  --sol-2:   var(--terre-100);
  --carte:   var(--blanc);
  --carte-2: var(--terre-50);      /* élévation : en nuit c'est une CLARTÉ */

  /* l'émail — le panneau marine ne bascule JAMAIS : c'est un aplat, pas une
     encre. Tout ce qui est un FOND bleu consomme --panneau, jamais --bleu. */
  --panneau:        var(--marine-800);
  --panneau-2:      var(--marine-900);
  --sur-panneau:    var(--marine-100);
  --sur-panneau-2:  var(--marine-200);

  /* encres */
  --encre:        var(--marine-900);
  --encre-2:      var(--terre-600);
  --encre-3:      var(--terre-500);
  --encre-accent: var(--marine-800);   /* titre de section, en-tête de tableau */

  /* traits — --trait-2 est PORTEUR (WCAG 1.4.11), --trait est décoratif */
  --trait:   var(--terre-200);
  --trait-2: var(--terre-500);

  /* liens */
  --lien:        var(--marine-800);
  --lien-survol: var(--or-800);

  /* les sens : aplat stable, encre qui bascule, voile qui bascule,
     bord porteur qui bascule (≥ 3:1 sur son propre voile). */
  --vert:        var(--vert-500);
  --vert-texte:  var(--vert-500);
  --vert-voile:  var(--vert-50);
  --vert-bord:   var(--vert-500);
  --rouge:       var(--rouge-500);
  --rouge-texte: var(--rouge-500);
  --rouge-voile: var(--rouge-50);
  --rouge-bord:  var(--rouge-500);
  --ambre:       var(--ambre-500);
  --ambre-texte: var(--ambre-500);
  --ambre-voile: var(--ambre-50);
  --ambre-bord:  var(--ambre-500);

  /* focus — un COUPLE, pas une couleur. La règle n'est pas « chaque anneau
     contraste » mais « au moins l'un des deux porte sur le fond ». Marine sur
     ivoire = 13,39:1 ; or sur marine = 8,06:1. Aucun fond du système
     n'échappe aux deux — y compris l'en-tête, où --focus seul faisait 1,00:1. */
  --focus:      var(--marine-800);
  --focus-halo: var(--or-500);

  /* élévation : la géométrie est écrite UNE fois, seule l'encre bascule. */
  --ombre-encre-1: rgba(16, 24, 56, .06);
  --ombre-encre-2: rgba(16, 24, 56, .22);
  --ombre-encre-3: rgba(16, 24, 56, .34);

  /* héritage — les 6 fichiers CSS et les 61 style="…" des vues continuent de
     fonctionner sans être touchés. Ces alias disparaîtront quand les vues
     auront été branchées sur les rôles ci-dessus.
     Effet de bord VOULU : --jaune-profond devient le bronze, ce qui répare
     d'un coup le rang de podium (2,91:1 → 5,61:1) et l'utilitaire .jaune. */
  --jaune:         var(--or-500);
  --jaune-vif:     #FFCE4A;               /* consommé par tv.css, inchangé */
  --jaune-profond: var(--or-800);
  --jaune-voile:   var(--or-100);
  --bleu:          var(--marine-800);     /* rôle réel : ENCRE d'accent */
  --bleu-texte:    var(--encre-accent);
  --bleu-nuit:     var(--marine-900);     /* aplat, jamais basculé */
  --bleu-abysse:   var(--marine-950);     /* aplat, jamais basculé */
  --bleu-doux:     #6B7590;
  --ivoire:        var(--terre-50);
}

/* ═══════════════════════════════════════ 3. LES DEUX THÈMES — light-dark() ══
   Une seule déclaration par jeton, les deux thèmes côte à côte. C'est ce qui
   rend structurellement impossible le défaut historique --bleu à 1,06:1 :
   on ne PEUT PAS déclarer une couleur dans un seul thème. */

@supports (color: light-dark(#000, #fff)) {
  :root {
    color-scheme: light dark;

    --sol:     light-dark(var(--terre-50),   var(--marine-950));
    --sol-2:   light-dark(var(--terre-100),  var(--marine-925));
    --carte:   light-dark(var(--blanc),      var(--marine-850));
    --carte-2: light-dark(var(--terre-50),   var(--marine-875));

    --encre:        light-dark(var(--marine-900), var(--terre-50));
    --encre-2:      light-dark(var(--terre-600),  var(--marine-300));
    --encre-3:      light-dark(var(--terre-500),  var(--marine-400));
    --encre-accent: light-dark(var(--marine-800), var(--marine-accent));

    --trait:   light-dark(var(--terre-200), var(--marine-750));
    --trait-2: light-dark(var(--terre-500), var(--marine-500));

    /* En Buvette l'or devient le lien : 9,57:1 sur la carte de nuit. C'est
       là qu'il vit — sur clair il ne dépasse jamais 1,84:1. */
    --lien:        light-dark(var(--marine-800), var(--or-300));
    --lien-survol: light-dark(var(--or-800),     var(--or-200));

    --vert-texte:  light-dark(var(--vert-500),  var(--vert-300));
    --vert-voile:  light-dark(var(--vert-50),   var(--vert-950));
    --vert-bord:   light-dark(var(--vert-500),  var(--vert-300));
    --rouge-texte: light-dark(var(--rouge-500), var(--rouge-300));
    --rouge-voile: light-dark(var(--rouge-50),  var(--rouge-950));
    --rouge-bord:  light-dark(var(--rouge-500), var(--rouge-300));
    --ambre-texte: light-dark(var(--ambre-500), var(--ambre-300));
    --ambre-voile: light-dark(var(--ambre-50),  var(--ambre-950));
    --ambre-bord:  light-dark(var(--ambre-500), var(--ambre-300));

    --focus:      light-dark(var(--marine-800), var(--or-400));
    --focus-halo: light-dark(var(--or-500),     var(--marine-950));

    --ombre-encre-1: light-dark(rgba(16, 24, 56, .06), rgba(0, 0, 0, .35));
    --ombre-encre-2: light-dark(rgba(16, 24, 56, .22), rgba(0, 0, 0, .70));
    --ombre-encre-3: light-dark(rgba(16, 24, 56, .34), rgba(0, 0, 0, .80));

    /* héritage */
    --jaune-profond: light-dark(var(--or-800), var(--or-300));
    --jaune-voile:   light-dark(var(--or-100), var(--marine-750));
    --bleu:          light-dark(var(--marine-800), var(--marine-accent));
    --bleu-doux:     light-dark(#6B7590, var(--marine-400));
  }

  /* Les deux lieux. Les noms historiques restent acceptés : aucune URL, aucun
     réglage enregistré, aucune vue TV ne casse. */
  :root[data-theme="cagnard"], :root[data-theme="clair"]  { color-scheme: light; }
  :root[data-theme="buvette"], :root[data-theme="sombre"] { color-scheme: dark;  }
}

/* ════════════════════════════════ 4. REPLI — navigateurs sans light-dark() ══
   Dupliqué par nécessité, mais SANS une seule valeur nouvelle : chaque ligne
   ne fait que pointer une primitive du § 1. Une couleur de nuit se corrige
   donc toujours à un seul endroit, jamais ici. */

@supports not (color: light-dark(#000, #fff)) {

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="cagnard"]):not([data-theme="clair"]) {
      color-scheme: dark;
      --sol: var(--marine-950);           --sol-2: var(--marine-925);
      --carte: var(--marine-850);         --carte-2: var(--marine-875);
      --encre: var(--terre-50);           --encre-2: var(--marine-300);
      --encre-3: var(--marine-400);       --encre-accent: var(--marine-accent);
      --trait: var(--marine-750);         --trait-2: var(--marine-500);
      --lien: var(--or-300);              --lien-survol: var(--or-200);
      --vert-texte: var(--vert-300);      --vert-voile: var(--vert-950);
      --vert-bord: var(--vert-300);
      --rouge-texte: var(--rouge-300);    --rouge-voile: var(--rouge-950);
      --rouge-bord: var(--rouge-300);
      --ambre-texte: var(--ambre-300);    --ambre-voile: var(--ambre-950);
      --ambre-bord: var(--ambre-300);
      --focus: var(--or-400);             --focus-halo: var(--marine-950);
      --ombre-encre-1: rgba(0, 0, 0, .35);
      --ombre-encre-2: rgba(0, 0, 0, .70);
      --ombre-encre-3: rgba(0, 0, 0, .80);
      --jaune-profond: var(--or-300);     --jaune-voile: var(--marine-750);
      --bleu: var(--marine-accent);       --bleu-doux: var(--marine-400);
    }
  }

  :root[data-theme="buvette"], :root[data-theme="sombre"] {
    color-scheme: dark;
    --sol: var(--marine-950);           --sol-2: var(--marine-925);
    --carte: var(--marine-850);         --carte-2: var(--marine-875);
    --encre: var(--terre-50);           --encre-2: var(--marine-300);
    --encre-3: var(--marine-400);       --encre-accent: var(--marine-accent);
    --trait: var(--marine-750);         --trait-2: var(--marine-500);
    --lien: var(--or-300);              --lien-survol: var(--or-200);
    --vert-texte: var(--vert-300);      --vert-voile: var(--vert-950);
    --vert-bord: var(--vert-300);
    --rouge-texte: var(--rouge-300);    --rouge-voile: var(--rouge-950);
    --rouge-bord: var(--rouge-300);
    --ambre-texte: var(--ambre-300);    --ambre-voile: var(--ambre-950);
    --ambre-bord: var(--ambre-300);
    --focus: var(--or-400);             --focus-halo: var(--marine-950);
    --ombre-encre-1: rgba(0, 0, 0, .35);
    --ombre-encre-2: rgba(0, 0, 0, .70);
    --ombre-encre-3: rgba(0, 0, 0, .80);
    --jaune-profond: var(--or-300);     --jaune-voile: var(--marine-750);
    --bleu: var(--marine-accent);       --bleu-doux: var(--marine-400);
  }
}

/* ══════════════════════════════════════════════════════ 5. LES MATIÈRES ════
   L'acier n'est pas une onzième couleur : c'est une FINITION. Une couleur
   s'ajoute à une palette et la dilue ; une finition s'applique à un seul
   objet et le distingue. Elle n'apparaît que là où il y a une BOULE.
   Le corps #6E778C fait 4,06:1 sur l'ivoire et 4,19:1 sur l'abysse : c'est la
   seule valeur du système qui tient des deux côtés sans être redéfinie. */

:root {
  --acier:
    /* 1. l'éclat spéculaire : dur, petit, décalé — c'est lui qui dit « métal » */
    radial-gradient(circle at 34% 26%, #FFFFFF 0 4%, #DDE3EE 9%, transparent 30%),
    /* 2. le reflet du comptoir : l'or vient du bas-droite, jamais du haut */
    radial-gradient(circle at 68% 78%, oklch(80.9% .162 81.4 / .55) 0 14%, transparent 46%),
    /* 3. le corps : froid, marine dans l'ombre */
    linear-gradient(158deg, #9AA3B8 0%, #6E778C 38%, #454C60 72%, #2A3042 100%);

  --acier-ombre:
    0 2px 3px oklch(22.2% .063 270 / .34),
    0 8px 18px -8px oklch(22.2% .063 270 / .45);

  /* Le bouchon est en buis : même construction MOINS l'éclat et MOINS le
     reflet. La différence de finition dit la différence de matière — on
     comprend au premier regard lequel des deux est en métal. */
  --bouchon: radial-gradient(circle at 38% 30%, #E8D9AE 0 18%, #C7A868 62%, #8E7332 100%);

  /* Le grain : le liant des quatre matières. ~430 octets en data: URI, zéro
     requête. baseFrequency .86 / 3 octaves reproduit la tuile de l'app à
     ±9 % autour du gris moyen — un grain de film, pas du bruit numérique. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.86' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='128' height='128' filter='url(%23g)'/%3E%3C/svg%3E");
  --grain-densite: .05;   /* Plein Cagnard : le papier est déjà texturé */
}

/* La densité n'est pas une couleur : light-dark() ne sait pas la porter.
   Deux règles, hors @supports, valables partout. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="cagnard"]):not([data-theme="clair"]) { --grain-densite: .14; }
}
:root[data-theme="buvette"], :root[data-theme="sombre"] { --grain-densite: .14; }

/* ══════════════════════════════════════════════════════ 6. LE MOUVEMENT ════
   Budget serré et assumé. Ce qui a le droit de bouger : le fond d'une ligne,
   l'opacité et 4 px de translation sur une modale, une barre de progression,
   la bascule d'une vue TV, la pulsation d'un squelette, et le trouble.
   Ce qui ne bouge JAMAIS : la position d'une ligne pendant la saisie, la
   largeur d'un bouton pendant son chargement, un chiffre pendant qu'il est lu.
   Le comité est debout avec son téléphone devant 97 équipes : un élément qui
   glisse sous le doigt, c'est un score faux. */

:root {
  --duree-1: 120ms;   /* survol, focus, pastille */
  --duree-2: 180ms;   /* bouton, ligne de tableau, message */
  --duree-3: 260ms;   /* modale, tiroir */
  --duree-4: 420ms;   /* flash de confirmation, transition de vue TV */
  --duree-trouble: 600ms;   /* LA CONSÉCRATION, et rien d'autre */

  /* Les noms sont ceux du rapport 01 § 7 et font foi côté site comme côté
     application. Les valeurs : --sortant décélère (ce qui ARRIVE se pose),
     --entrant accélère (ce qui PART s'échappe). */
  --sortant: cubic-bezier(.16, 1, .3, 1);
  --entrant: cubic-bezier(.7, 0, .84, 0);

  /* Le trouble du pastis n'est pas une interpolation linéaire : rien ne se
     passe, puis un front traverse le verre, puis ça se stabilise. D'où cette
     courbe, et elle ne sert qu'à ça. Quatre emplois autorisés : podium,
     record battu, clôture de journée, remise des prix en mode TV. */
  --louche: cubic-bezier(.22, .61, .36, 1);
}

/* ═══════════════════════════════════════════════════════ 7. LES GRAPHES ════
   Séparés des couleurs de marque parce qu'une couleur de tracé a une
   contrainte que --jaune n'a pas : 3:1 minimum contre la surface qui
   l'entoure (WCAG 1.4.11). L'or faisant 1,84:1 sur blanc, il n'est jamais un
   trait fin ni le seul porteur d'une information : il est une surface large,
   cerclée de --chart-or-trait, et toujours doublée d'une étiquette. */

:root {
  --chart-or:        var(--or-500);
  --chart-or-trait:  #B47C06;            /* 3,60:1 sur blanc — le cerclage */
  --chart-or-voile:  var(--or-100);
  --chart-eau:       #8C7434;            /* la journée écartée : 1 pt de présence */
  --chart-trouble:   var(--terre-50);
  --chart-moi:       var(--marine-800);
  --chart-axe:       var(--terre-600);   /* 6,02:1 sur blanc */
  --chart-grille:    var(--terre-200);
  --chart-neutre:    #6B7590;            /* 4,59:1 sur blanc */
  --chart-repere:    var(--ambre-500);   /* la moyenne, en pointillés */
}

@supports (color: light-dark(#000, #fff)) {
  :root {
    --chart-or:       light-dark(var(--or-500),    #FFCE4A);
    --chart-or-trait: light-dark(#B47C06,          #FFCE4A);
    --chart-or-voile: light-dark(var(--or-100),    var(--marine-750));
    --chart-eau:      light-dark(#8C7434,          #C9B48A);
    --chart-moi:      light-dark(var(--marine-800), var(--marine-accent));
    --chart-axe:      light-dark(var(--terre-600), var(--marine-300));
    --chart-grille:   light-dark(var(--terre-200), var(--marine-750));
    --chart-neutre:   light-dark(#6B7590,          var(--marine-400));
    --chart-repere:   light-dark(var(--ambre-500), var(--ambre-300));
  }
}

@supports not (color: light-dark(#000, #fff)) {
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="cagnard"]):not([data-theme="clair"]) {
      --chart-or: #FFCE4A;               --chart-or-trait: #FFCE4A;
      --chart-or-voile: var(--marine-750); --chart-eau: #C9B48A;
      --chart-moi: var(--marine-accent); --chart-axe: var(--marine-300);
      --chart-grille: var(--marine-750); --chart-neutre: var(--marine-400);
      --chart-repere: var(--ambre-300);
    }
  }
  :root[data-theme="buvette"], :root[data-theme="sombre"] {
    --chart-or: #FFCE4A;               --chart-or-trait: #FFCE4A;
    --chart-or-voile: var(--marine-750); --chart-eau: #C9B48A;
    --chart-moi: var(--marine-accent); --chart-axe: var(--marine-300);
    --chart-grille: var(--marine-750); --chart-neutre: var(--marine-400);
    --chart-repere: var(--ambre-300);
  }
}

/* ══════════════════════════════════ 8. RYTHME, ÉLÉVATION, TYPOGRAPHIE ══════ */

:root {
  /* élévation — une géométrie, une encre qui bascule (§ 2 et § 4) */
  --ombre-1: 0 1px 2px var(--ombre-encre-1);
  --ombre-2: 0 1px 2px var(--ombre-encre-1), 0 8px 24px -10px var(--ombre-encre-2);
  --ombre-3: 0 2px 4px var(--ombre-encre-1), 0 20px 44px -16px var(--ombre-encre-3);

  /* rythme */
  --r1: .25rem;  --r2: .5rem;   --r3: .75rem;  --r4: 1rem;
  --r5: 1.5rem;  --r6: 2rem;    --r7: 3rem;    --r8: 4.5rem;
  --arrondi:   10px;
  --arrondi-2: 16px;
  --gouttiere: clamp(1rem, 4vw, 2rem);
  --largeur:   1180px;

  /* typographie — deux familles, auto-hébergées (assets/css/polices.css).
     La pile de secours compte : si un .woff2 manque, l'affiche doit rester
     une affiche. « Archivo Narrow » n'était jamais chargée, elle ne servait
     à rien ; Arial Narrow et Haettenschweiler, elles, existent sur le parc. */
  --titre: "Big Shoulders Display", "Arial Narrow", Haettenschweiler, Impact, sans-serif;
  --texte: Archivo, "Segoe UI", system-ui, -apple-system, "Helvetica Neue", sans-serif;
}

/* ═══════════════════════════════════════════════════════════ 9. LE TROUBLE ══
   La consécration, et rien d'autre. Le trouble du pastis n'est pas une
   interpolation linéaire et ce n'est pas non plus un ease-out : rien ne se
   passe, puis un front de turbidité traverse le verre, puis ça se stabilise.
   D'où --louche (§ 6), et d'où une propriété ENREGISTRÉE : sans @property,
   « transition: --trouble » ne veut rien dire — le navigateur ne sait pas
   interpoler un jeton qu'il n'a pas typé, et la couleur sauterait d'un bloc.
   Elle se déclare hors couche, comme toutes les règles @ : une couche ne
   changerait rien à son effet et la rendrait plus difficile à trouver.

   Quatre emplois autorisés, pas cinq : le podium à la clôture, un record
   personnel battu, une clôture de journée réussie côté comité, la bascule
   « remise des prix » en mode TV. Un effet de consécration qui sert à
   confirmer un formulaire n'est plus une consécration.
   Durée et courbe : .travaux/jetons/mouvement.json, source de vérité du site
   ET de l'application. On modifie LÀ-BAS d'abord. */

@property --trouble {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

/* ────────────────────────────────────────────────────────────────── SOCLE ── */

@layer socle {
  *, *::before, *::after { box-sizing: border-box; }

  html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
  @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

  /* LES GRANDS CARACTÈRES — trois crans, posés sur <html> par le script
     d'amorçage de site.php, donc AVANT le premier peint : le texte ne grossit
     jamais sous les yeux du lecteur. Tout le fichier étant en rem, l'interface
     entière suit — y compris les cibles tactiles, qui grandissent avec leurs
     marges. 100 % vaut la taille choisie DANS le navigateur : un joueur qui a
     déjà agrandi son téléphone garde son réglage et le multiplie.
     C'est aussi pour ça que body est en 1rem et non plus en 16px : une taille
     en pixels sur body aurait figé tout le texte courant à 16 px et rendu le
     réglage inopérant là où il sert le plus — les paragraphes. */
  html[data-texte="grand"]      { font-size: 112.5%; }
  html[data-texte="tres-grand"] { font-size: 125%; }

  body {
    margin: 0;
    min-height: 100dvh;
    background: var(--sol);
    color: var(--encre);
    font-family: var(--texte);
    font-size: 1rem;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  /* Le grain — le liant des quatre matières. Une tuile de 128 px répétée, en
     fondu « overlay » : sur l'ivoire elle imite le papier, sur le marine elle
     casse l'aplat nu. Aucune requête (data: URI), aucun nœud dans le DOM.
     L'écran de buvette en est exclu : il tourne huit heures d'affilée sur une
     box Android, et son habillage appartient à tv.css.
     z-index 1 et pas 0 : à 0, le grain se range parmi les éléments POSITIONNÉS
     à z-index automatique et se fait donc recouvrir par le premier d'entre eux
     qui vient après lui dans l'arbre — au premier chef la Scène, qui est en
     position:relative. La photo du bandeau, celle qui a le plus besoin de
     grain, était précisément la seule à ne pas en recevoir. */
  body:not(.tv)::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background-image: var(--grain);
    background-repeat: repeat;
    background-size: 128px 128px;
    opacity: var(--grain-densite);
    mix-blend-mode: overlay;
  }
  @media (prefers-reduced-transparency: reduce) { body::before { display: none; } }
  @media print { body::before { display: none !important; } }

  /* Bascule de thème : jamais de « * { transition } ». On cible les trois
     propriétés qui changent, sur les surfaces seules — sinon le passage
     Cagnard → Buvette fait tressauter toute la page. */
  @media (prefers-reduced-motion: no-preference) {
    :where(body, .carte, .entete, .pied, .ligne-classement, .journee,
           .rencontre, .nom-joueur, .stats > div, .message, .tableau td,
           .tableau th) {
      transition: background-color var(--duree-2) var(--sortant),
                  color            var(--duree-2) var(--sortant),
                  border-color     var(--duree-2) var(--sortant);
    }
  }

  /* Discipline typographique — trois défauts corrigés, tous mesurés.
     T1 · le letter-spacing était POSITIF (.012em) sur un h1 qui monte à
          57,6 px. Une condensée très grasse en grand corps se resserre, elle
          ne s'aère pas : -.022em sur h1, -.012em sur h2, 0 en dessous.
     T2 · h4 était en Big Shoulders à 18,4 px. Sous 1,2 rem la condensation ne
          gagne plus de place et coûte de la lisibilité — sur un lectorat de
          55-75 ans, à bout de bras, dehors. h3 et h4 QUITTENT la famille
          d'affiche pour Archivo 700 ; Big Shoulders ne descend plus sous
          1,44 rem.
     T3 · line-height .92 s'appliquait aussi à h4 : à 18 px, sur deux lignes,
          les jambages se touchent. Interlignage par palier. */
  h1, h2, h3, h4 {
    text-wrap: balance;
    margin: 0;
  }
  h1, h2 { font-family: var(--titre); font-weight: 800; }
  h1 { font-size: clamp(2.2rem, 5.5vw, 3.6rem); line-height: .88; letter-spacing: -.022em; }
  h2 { font-size: clamp(1.7rem, 3.6vw, 2.5rem); line-height: .95; letter-spacing: -.012em; }
  h3, h4 { font-family: var(--texte); font-weight: 700; line-height: 1.25; letter-spacing: 0; }
  h3 { font-size: 1.4rem; }
  h4 { font-size: 1.15rem; }

  p { margin: 0 0 var(--r4); }
  p:last-child { margin-bottom: 0; }

  /* Un seul jeton pour le lien, donc une seule ligne au lieu des trois
     règles de bascule qu'il fallait tenir à la main. En Buvette, l'or prend
     le rôle : 9,57:1 sur la carte de nuit. */
  a { color: var(--lien); text-decoration-thickness: 1.5px; text-underline-offset: 2px; }
  a:hover { color: var(--lien-survol); }

  img, svg, video { max-width: 100%; height: auto; display: block; }

  button, input, select, textarea { font: inherit; color: inherit; }

  :focus-visible {
    outline: 3px solid var(--focus);
    outline-offset: 2px;
    box-shadow: 0 0 0 6px var(--focus-halo);
    border-radius: 3px;
  }

  table { border-collapse: collapse; width: 100%; }

  ::selection { background: var(--jaune); color: var(--bleu-nuit); }
}

/* ───────────────────────────────────────────────────────────── COMPOSANTS ── */

@layer composants {

  /* — mise en page — */

  .enveloppe { max-width: var(--largeur); margin-inline: auto; padding-inline: var(--gouttiere); }
  .etroit    { max-width: 780px; }

  .pile   { display: flex; flex-direction: column; gap: var(--r4); }
  .pile-2 { display: flex; flex-direction: column; gap: var(--r2); }
  .rang   { display: flex; align-items: center; gap: var(--r3); flex-wrap: wrap; }
  .rang-serre { display: flex; align-items: center; gap: var(--r2); }
  .entre  { justify-content: space-between; }
  .pousse { margin-left: auto; }

  .grille   { display: grid; gap: var(--r4); }
  .grille-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
  .grille-3 { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
  .grille-4 { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }

  .section { padding-block: clamp(var(--r6), 6vw, var(--r8)); }
  .section-mince { padding-block: var(--r6); }

  /* — bande signature : la capsule de la bouteille — */

  /* La capsule de la bouteille, abstraite juste ce qu'il faut. C'est le seul
     motif décoratif autorisé du système, et il voyage : en-tête du site, pied
     de l'affiche partageable, filet de tête des trois feuilles, bord du
     sous-bock, séparateur de vues en mode TV. Un motif, six emplois. */
  .bande {
    height: 7px;
    background: repeating-linear-gradient(-58deg, var(--jaune) 0 18px, var(--panneau) 18px 23px);
  }

  /* — bandeau de demonstration : le site doit dire ce qu'il montre — */

  .bandeau-demo {
    display: flex; align-items: center; justify-content: center; gap: .5rem;
    margin: 0; padding: .55rem var(--gouttiere);
    background: var(--ambre-voile); color: var(--ambre);
    font-size: .82rem; font-weight: 600; text-align: center;
  }
  .bandeau-demo strong { font-weight: 800; }
  @media (max-width: 640px) { .bandeau-demo { font-size: .75rem; align-items: flex-start; text-align: left; } }

  /* — en-tete du site — */

  .entete {
    position: sticky; top: 0; z-index: 40;
    background: var(--panneau);
    color: var(--blanc);
    box-shadow: var(--ombre-1);
  }
  .entete .enveloppe { display: flex; align-items: center; gap: var(--r4); min-height: 62px; }

  .marque { display: flex; align-items: center; gap: .6rem; text-decoration: none; color: inherit; flex: none; }
  /* La boule de la marque n'est plus une goutte dorée : elle est dessinée par
     la recette d'acier, plus bas, avec .boule-acier — un seul dessin pour la
     marque, le podium, le carreau, le chargeur et l'état vide. */
  .marque .mot {
    font-family: var(--titre); font-weight: 900; font-size: 1.4rem;
    line-height: .88; letter-spacing: .02em; color: var(--jaune);
  }
  .marque .mot small { display: block; font-size: .5rem; letter-spacing: .24em; color: #B9C0DE; font-weight: 700; }

  .nav { display: flex; gap: .15rem; margin-left: auto; align-items: center; }
  .nav a {
    padding: .5rem .75rem; border-radius: var(--arrondi);
    color: #D3D9EC; text-decoration: none; font-weight: 600; font-size: .92rem;
    white-space: nowrap;
  }
  .nav a:hover { background: rgba(255, 255, 255, .1); color: var(--blanc); }
  .nav a[aria-current="page"] { background: var(--jaune); color: var(--bleu-nuit); }

  .bascule-nav {
    display: none; margin-left: auto;
    background: rgba(255, 255, 255, .12); border: 0; border-radius: var(--arrondi);
    padding: .45rem .6rem; color: var(--blanc); cursor: pointer;
  }

  /* — barre d'onglets : sur telephone, le site se pilote comme l'application — */

  .barre-onglets { display: none; }

  @media (max-width: 900px) {
    .bascule-nav { display: none; }
    .nav {
      position: absolute; inset: 62px 0 auto 0;
      flex-direction: column; align-items: stretch; gap: 0;
      background: var(--panneau); padding: var(--r2) var(--gouttiere) var(--r4);
      box-shadow: var(--ombre-2); margin-left: 0;
    }
    .nav { display: none; }
  }

  @media (max-width: 900px) {
    .barre-onglets {
      position: fixed; inset: auto 0 0 0; z-index: 50;
      display: grid; grid-template-columns: repeat(5, 1fr);
      background: var(--carte);
      border-top: 2px solid var(--bleu);
      padding: .4rem .25rem calc(.4rem + env(safe-area-inset-bottom));
      box-shadow: 0 -2px 16px rgba(16, 24, 56, .12);
    }
    .barre-onglets a {
      display: flex; flex-direction: column; align-items: center; gap: .15rem;
      padding: .35rem .2rem; border-radius: var(--arrondi);
      color: var(--encre-2); text-decoration: none;
      font-size: .66rem; font-weight: 700; letter-spacing: .01em;
      min-height: 48px; justify-content: center;
    }
    .barre-onglets a[aria-current="page"] { color: var(--bleu-texte); }
    .barre-onglets a[aria-current="page"] .rond {
      background: var(--jaune); color: var(--bleu-nuit);
    }
    .barre-onglets .rond {
      display: grid; place-items: center;
      width: 34px; height: 26px; border-radius: 999px;
      transition: background .16s ease;
    }
    .barre-onglets .puce {
      position: absolute; transform: translate(11px, -9px);
      min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
      background: var(--rouge); color: #fff;
      font-size: .6rem; font-weight: 800; display: grid; place-items: center;
    }

    /* De la place pour la barre, et pour l'encoche du bas. */
    body { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
    .pied { margin-bottom: 0; }
  }

  /* — cartes — */

  .carte {
    background: var(--carte);
    border: 1px solid var(--trait);
    border-radius: var(--arrondi-2);
    padding: var(--r4);
  }
  .carte-nette { border-radius: var(--arrondi); }
  .carte-bleue { background: var(--panneau); border-color: var(--panneau); color: var(--blanc); }
  .carte-bleue h1, .carte-bleue h2, .carte-bleue h3 { color: var(--jaune); }
  .carte-jaune { background: var(--jaune); border-color: var(--jaune-profond); color: var(--bleu-nuit); }
  .carte-cerclee { border: 2px solid var(--jaune); }
  .carte-lien { text-decoration: none; color: inherit; display: block; transition: border-color .15s, transform .15s, box-shadow .15s; }
  .carte-lien:hover { border-color: var(--jaune); box-shadow: var(--ombre-2); color: inherit; }
  @media (prefers-reduced-motion: no-preference) { .carte-lien:hover { transform: translateY(-2px); } }

  /* — pastilles d'etat — */

  .pastille {
    display: inline-flex; align-items: center; gap: .3rem;
    padding: .18rem .5rem; border-radius: 999px;
    font-size: .68rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
    background: var(--jaune); color: var(--bleu-nuit); white-space: nowrap;
  }
  .pastille-bleue  { background: var(--panneau); color: var(--jaune); }
  .pastille-verte  { background: var(--vert-voile); color: var(--vert-texte); }
  .pastille-rouge  { background: var(--rouge-voile); color: var(--rouge-texte); }
  .pastille-ambre  { background: var(--ambre-voile); color: var(--ambre-texte); }
  .pastille-neutre { background: var(--sol-2); color: var(--encre-2); }
  .pastille-vide   { background: transparent; color: var(--encre-2); border: 1.5px solid var(--trait-2); }

  /* — boutons — */

  /* Le bouton d'action porte le dessin de BoutonOr (terrain.dart:377-455),
     au chiffre près : dégradé vertical jaune-vif → #F0A80C, reflet d'arête
     blanc 55 % sur le bord haut, ombre dorée courte. Ce n'est PAS un dégradé
     décoratif au sens du commandement II : c'est le bouton signature de
     l'app, porté ici pour que la loi d'airain tienne. Les variantes (bleu,
     vert, vide, danger) restent des aplats — un seul bouton brille. */
  .bouton {
    display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
    padding: .7rem 1.1rem; border: 2px solid transparent; border-radius: var(--arrondi);
    background: linear-gradient(180deg, var(--jaune-vif), #F0A80C); color: var(--bleu-nuit);
    border-top-color: rgba(255, 255, 255, .55);
    box-shadow: 0 10px 22px -14px oklch(from var(--jaune) l c h / .8);
    font-weight: 800; font-size: .95rem; text-decoration: none; cursor: pointer;
    transition: filter .15s, transform .1s;
  }
  .bouton:hover  { filter: brightness(1.06); color: var(--bleu-nuit); }
  .bouton:active { transform: translateY(1px); }
  .bouton[disabled], .bouton[aria-disabled="true"] { opacity: .5; pointer-events: none; box-shadow: none; }
  .bouton-bleu   { background: var(--panneau); color: var(--jaune); border-top-color: transparent; box-shadow: none; }
  .bouton-bleu:hover { color: var(--jaune-vif); }
  .bouton-vert   { background: var(--vert); color: var(--blanc); border-top-color: transparent; box-shadow: none; }
  .bouton-vert:hover { color: var(--blanc); }
  .bouton-vide   { background: transparent; border-color: var(--trait-2); color: var(--encre); }
  .bouton-vide:hover { border-color: var(--bleu); color: var(--encre); }
  .bouton-danger { background: var(--rouge); color: var(--blanc); border-top-color: transparent; box-shadow: none; }
  .bouton-danger:hover { color: var(--blanc); }
  .bouton-petit  { padding: .4rem .7rem; font-size: .82rem; }
  .bouton-large  { width: 100%; }

  /* — formulaires — */

  .champ { display: flex; flex-direction: column; gap: .3rem; }
  .champ > label { font-size: .78rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--encre-2); }
  .champ input[type="text"], .champ input[type="password"], .champ input[type="email"],
  .champ input[type="tel"], .champ input[type="number"], .champ input[type="date"],
  .champ input[type="time"], .champ input[type="search"], .champ select, .champ textarea {
    width: 100%; padding: .6rem .75rem;
    background: var(--carte); color: var(--encre);
    border: 1.5px solid var(--trait-2); border-radius: var(--arrondi);
  }
  .champ input:focus, .champ select:focus, .champ textarea:focus {
    border-color: var(--bleu); outline: 3px solid var(--jaune); outline-offset: 1px;
  }
  .champ input:user-invalid { border-color: var(--rouge); }
  .champ .aide { font-size: .8rem; color: var(--encre-2); }
  .champ-inline { display: flex; align-items: center; gap: .5rem; }
  .champ-inline label { text-transform: none; letter-spacing: 0; font-weight: 600; font-size: .95rem; color: var(--encre); }

  /* — messages — */

  .message {
    display: flex; gap: .6rem; align-items: flex-start;
    padding: .8rem 1rem; border-radius: var(--arrondi);
    border-left: 4px solid var(--bleu); background: var(--sol-2);
    font-size: .93rem;
  }
  .message-succes { border-left-color: var(--vert); background: var(--vert-voile); color: var(--vert-texte); }
  .message-erreur { border-left-color: var(--rouge); background: var(--rouge-voile); color: var(--rouge-texte); }
  .message-info   { border-left-color: var(--jaune); background: var(--jaune-voile); }

  /* — tableaux de donnees — */

  .tableau { font-variant-numeric: tabular-nums; font-size: .93rem; }
  .tableau th {
    text-align: left; padding: 0 .6rem .5rem;
    font-size: .68rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
    color: var(--bleu-texte); border-bottom: 2px solid var(--bleu-texte);
    white-space: nowrap;
  }
  .tableau td { padding: .6rem; border-bottom: 1px solid var(--trait); }
  .tableau tbody tr:last-child td { border-bottom: 0; }
  .tableau tbody tr:hover { background: var(--jaune-voile); }
  .tableau .nombre { text-align: right; }
  .defile { overflow-x: auto; -webkit-overflow-scrolling: touch; }

  /* Sur telephone, un tableau ne se fait pas defiler : il se simplifie.
     Les colonnes de confort disparaissent, l'essentiel reste lisible. */
  @media (max-width: 720px) {
    .tableau .col-confort { display: none; }
    .tableau th, .tableau td { padding: .55rem .35rem; }
    .tableau th { font-size: .6rem; letter-spacing: .08em; }
    .tableau { font-size: .88rem; }
  }

  /* — chiffres — */

  .chiffre { font-family: var(--titre); font-weight: 900; line-height: .9; font-variant-numeric: tabular-nums; }
  .chiffre-xl { font-size: clamp(2.2rem, 5vw, 3.2rem); }
  .chiffre-l  { font-size: 1.9rem; }
  .chiffre-m  { font-size: 1.35rem; }

  .etiquette {
    font-size: .68rem; font-weight: 800; letter-spacing: .14em;
    text-transform: uppercase; color: var(--encre-2);
  }

  /* — jeton joueur — */

  .jeton {
    width: 34px; height: 34px; border-radius: 50%; flex: none;
    display: grid; place-items: center;
    background: var(--panneau); color: var(--jaune);
    font-size: .78rem; font-weight: 800; letter-spacing: .02em;
  }
  .jeton-or { background: var(--jaune); color: var(--bleu-nuit); }
  .jeton-l  { width: 54px; height: 54px; font-size: 1.15rem; }

  /* — surtitre — */

  .surtitre {
    display: flex; align-items: center; gap: .6rem;
    font-size: .7rem; font-weight: 800; letter-spacing: .2em; text-transform: uppercase;
    color: var(--encre-2); margin-bottom: .5rem;
  }
  .surtitre::before { content: ""; width: 24px; height: 3px; background: var(--jaune); flex: none; }

  /* — pied — */

  .pied { background: var(--bleu-nuit); color: #C6CBE0; padding-block: var(--r6) var(--r7); margin-top: var(--r7); }
  .pied a { color: var(--jaune); }
  .pied strong { color: var(--jaune); }

  /* — les reglages d'affichage, en pied de page —
     Deux réglages, un seul mécanisme : un groupe de boutons, une clé dans le
     navigateur, un attribut sur <html>. Rien n'est envoyé au serveur, donc
     rien à consentir. Les deux blocs restent « hidden » tant que app.js n'a
     pas pris la main — un bouton mort se remarque plus qu'un bouton absent.
     D'où le :not([hidden]) : sans lui, ce display:inline-block écraserait la
     règle d'agent utilisateur et les boutons apparaîtraient sans JavaScript. */
  .pied-theme:not([hidden]),
  .pied-texte:not([hidden]) {
    display: inline-block;
    vertical-align: top;
    margin: var(--r5) var(--r6) 0 0;
  }
  .pied .intitule {
    margin: 0 0 .45rem;
    font-size: .7rem; font-weight: 800; letter-spacing: .18em;
    text-transform: uppercase; color: var(--jaune);
  }
  .pied .aide { margin: .5rem 0 0; font-size: .78rem; max-width: 44ch; }

  .choix-theme, .choix-texte {
    display: inline-flex; flex-wrap: wrap; gap: 2px;
    padding: 3px; border-radius: 999px;
    background: rgba(255, 255, 255, .08);
  }
  .choix-theme button, .choix-texte button {
    min-height: 44px;            /* cible tactile : on règle ça avec des gants */
    padding: .5rem .95rem; border: 0; border-radius: 999px;
    background: transparent; color: #C6CBE0;
    font-weight: 700; font-size: .85rem; white-space: nowrap; cursor: pointer;
    transition: background-color var(--duree-1) var(--sortant),
                color            var(--duree-1) var(--sortant);
  }
  .choix-theme button:hover, .choix-texte button:hover { background: rgba(255, 255, 255, .14); color: var(--blanc); }
  .choix-theme button[aria-pressed="true"],
  .choix-texte button[aria-pressed="true"] { background: var(--jaune); color: var(--bleu-nuit); }

  /* ══════════════════════════ LA SIGNATURE — l'acier, le buis, le trouble ══
     Le seul objet du système qui a le droit de briller est la boule. Tout le
     reste est mat comme de l'émail et plat comme du papier punaisé. */

  /* — LA BOULE D'ACIER — trois couches, aucune image, aucune dépendance.
       Les centres des dégradés sont en POURCENTAGE : la recette vaut donc à
       22, 30 et 54 px sans être réécrite une seule fois. Le corps #6E778C
       mesure 4,06:1 sur l'ivoire et 4,19:1 sur l'abysse — au-dessus du seuil
       de 3:1 d'un élément graphique porteur d'information (WCAG 1.4.11), et
       dans les DEUX thèmes sans être redéfini. C'est ce qui fait de la boule
       le seul objet qui unifie Plein Cagnard et La Buvette.
       Le liseré intérieur de 1 px n'est pas un reflet de plus : c'est ce qui
       détache le bas de la sphère (#2A3042) de l'émail du panneau (#1A2452),
       où sans lui la boule se lit en croissant. Recette et valeurs :
       .travaux/jetons/couleurs.json → « acier ». */
  .boule-acier,
  .marque .boule {
    /* La taille par défaut vit dans le REPLI de var(), pas dans une
       déclaration : ainsi un conteneur qui pose --boule-taille (le chargeur,
       une ligne de podium) commande toutes les boules qu'il contient, au lieu
       de se faire écraser par la valeur locale de chaque boule. */
    width: var(--boule-taille, 30px);
    height: var(--boule-taille, 30px);
    flex: none;
    display: inline-block;
    border-radius: 50%;
    background: var(--acier);
    box-shadow: var(--acier-ombre), inset 0 0 0 1px rgba(255, 255, 255, .12);
  }
  .boule-petite { --boule-taille: 22px; }
  .boule-grande { --boule-taille: 54px; }

  /* — LE BOUCHON — la même construction MOINS l'éclat et MOINS le reflet.
       Deux objets, deux finitions, zéro légende nécessaire : on comprend au
       premier regard lequel est en métal. Le buis est un bois pâle et mat ;
       son ombre est courte, il est POSÉ sur le papier, pas suspendu. */
  .bouchon-buis {
    --boule-taille: 14px;
    width: var(--boule-taille);
    height: var(--boule-taille);
    flex: none;
    display: inline-block;
    border-radius: 50%;
    background: var(--bouchon);
    box-shadow: 0 1px 2px var(--ombre-encre-2);
  }

  /* — LE CHARGEUR — la boule qui roule. Le seul « linear » autorisé du
       système : une boucle infinie qui accélère et ralentit donne le tournis.
       La course vaut la circonférence (πd), donc la boule roule sans patiner —
       ça se voit, même à 22 px. */
  .chargeur {
    --boule-taille: 22px;        /* la taille se règle ICI : piste et boule suivent */
    display: inline-flex; align-items: center; gap: var(--r3);
    color: var(--encre-2); font-size: .92rem; font-weight: 700;
  }
  .chargeur .piste {
    position: relative; flex: none;
    width: calc(var(--boule-taille) * 4.15);
    height: var(--boule-taille);
  }
  .chargeur .boule-acier {
    position: absolute; inset: 0 auto auto 0;
    animation: ppb-roule 900ms linear infinite;
  }
  @keyframes ppb-roule {
    0%   { transform: translateX(0) rotate(0deg); }
    50%  { transform: translateX(calc(var(--boule-taille, 22px) * 3.1416)) rotate(360deg); }
    100% { transform: translateX(0) rotate(0deg); }
  }
  /* Le mouvement part, l'attente reste dite : une boule immobile ne dit pas
     qu'on travaille. La pulsation d'un squelette est au budget (§ 6). */
  @keyframes ppb-souffle { 50% { opacity: .4; } }
  @media (prefers-reduced-motion: reduce) {
    .chargeur .boule-acier { animation: ppb-souffle 1.6s ease-in-out infinite; }
  }

  /* — L'ÉTAT VIDE — le cercle tracé au pied dans le gravier, et SANS boule
       dedans : « rien à classer tant qu'aucun score n'est saisi » devient une
       image avant d'être une phrase. L'arc fait 300° et s'ouvre en bas, un peu
       de travers — un cercle parfait serait un chargeur, pas un tracé.
       Le masque conique découpe l'ouverture : le repli, sur un navigateur qui
       ignore mask-image, est un cercle complet. Personne n'y perd. */
  .etat-vide {
    display: grid; justify-items: center; gap: var(--r4);
    padding: var(--r7) var(--r4);
    text-align: center; color: var(--encre-2);
  }
  .etat-vide::before {
    content: "";
    width: 92px; height: 92px;
    border: 3px solid var(--trait-2);
    border-radius: 50%;
    transform: rotate(-8deg);
    -webkit-mask-image: conic-gradient(from 210deg, #000 0 300deg, transparent 300deg);
            mask-image: conic-gradient(from 210deg, #000 0 300deg, transparent 300deg);
  }
  .etat-vide h2, .etat-vide h3, .etat-vide .titre { margin: 0; color: var(--encre); }
  .etat-vide p { margin: 0; max-width: 42ch; }

  /* — LE VERRE — le panneau translucide de l'application. Le détail qui fait
       l'épaisseur n'est pas le flou : c'est le LISERÉ CLAIR d'1 px sur l'arête
       HAUTE, la tranche qui prend la lumière. En Buvette, l'élévation est une
       clarté et non une ombre — ce liseré est exactement cette clarté.
       Deux replis, parce qu'un panneau translucide sans flou derrière lui est
       illisible : pas de backdrop-filter, ou transparence refusée par le
       système, et le verre redevient une carte pleine. */
  .verre {
    position: relative;
    padding: var(--r4);
    border: 1px solid var(--trait);
    border-radius: var(--arrondi-2);
    background: color-mix(in oklch, var(--carte) 78%, transparent);
    -webkit-backdrop-filter: blur(12px) saturate(1.15);
            backdrop-filter: blur(12px) saturate(1.15);
  }
  .verre::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    border: 1px solid transparent;
    border-top-color: rgba(255, 255, 255, .45);
    border-radius: inherit;
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .verre { background: var(--carte); }
  }
  @media (prefers-reduced-transparency: reduce) {
    .verre {
      background: var(--carte);
      -webkit-backdrop-filter: none;
              backdrop-filter: none;
    }
  }

  /* — LA FEUILLE — l'émail marine qui REMONTE sur la photo. Rayon en haut
       seulement, liseré d'arête, et une ombre portée vers le HAUT : elle est
       posée sur la scène, pas suspendue au-dessus d'elle.
       Le panneau ne bascule jamais de thème — c'est un aplat, pas une encre —
       d'où --sur-panneau pour le texte, qui tient sur le marine des deux
       côtés. */
  .feuille {
    position: relative;
    padding: var(--r5) var(--gouttiere) var(--r6);
    border-radius: 30px 30px 0 0;
    background: var(--panneau-2);
    color: var(--sur-panneau);
    box-shadow: 0 -14px 34px -14px var(--ombre-encre-3);
  }
  .feuille::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    border: 1px solid transparent;
    border-top-color: rgba(255, 255, 255, .10);
    border-radius: inherit;
  }
  .feuille > .enveloppe { position: relative; padding-inline: 0; }
  .feuille h1, .feuille h2, .feuille h3 { color: var(--jaune); }
  .feuille a { color: var(--or-300); }
  .scene + .feuille, .bandeau-photo + .feuille { margin-top: -30px; }

  /* — LE TROUBLE — la consécration, et rien d'autre (§ 9 pour la propriété
       enregistrée et les quatre emplois autorisés). L'encre est fixée au
       marine : la surface part de l'or et arrive à l'ivoire, deux fonds
       clairs, donc elle n'a aucune raison de basculer avec le thème. */
  .se-trouble {
    background: color-mix(in oklch, var(--or-500), var(--terre-50) var(--trouble));
    color: var(--marine-900);
    transition: --trouble var(--duree-trouble) var(--louche);
  }
  .se-trouble.est-trouble { --trouble: 100%; }
  @media (prefers-reduced-motion: reduce) {
    .se-trouble { transition-duration: 150ms; }   /* le fondu reste, le suspense part */
  }

  /* — LA GRILLE DE L'AFFICHE — un rail étroit, un corps large, un comptoir.
       Une feuille de concours punaisée n'est jamais une suite de colonnes
       égales : elle a une colonne de tête — la date, le rang, le n° de jeu —
       que l'œil descend en diagonale. Le rail est CONSTANT d'une page à
       l'autre : un joueur qui a appris à lire une page les a toutes apprises.
       Sous 900 px le comptoir tombe sous le corps ; sous 640 px le rail se
       resserre mais ne disparaît jamais, c'est lui qui rend la page
       parcourable. */
  .rail-grille {
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 7fr) minmax(0, 3fr);
    gap: var(--r4) var(--r5);
    align-items: start;
  }
  .rail-grille > .rail { text-align: center; }
  .rail-grille > .rail .chiffre,
  .rail-grille > .rail .etiquette { display: block; }
  .rail-grille > .rail .chiffre { color: var(--encre-accent); }
  .rail-grille > .comptoir { grid-column: 3; grid-row: 1 / -1; }

  @media (max-width: 900px) {
    .rail-grille { grid-template-columns: 4.5rem minmax(0, 1fr); }
    .rail-grille > .comptoir { grid-column: 1 / -1; grid-row: auto; }
  }
  @media (max-width: 640px) {
    .rail-grille { grid-template-columns: 3.4rem minmax(0, 1fr); gap: var(--r3) var(--r4); }
  }
}

/* ───────────────────────────────────────────────────────────────── PAGES ── */

@layer pages {

  /* — banniere de page — */

  .banniere { background: var(--jaune); color: var(--bleu-nuit); padding-block: var(--r6); }
  @media (max-width: 640px) { .banniere { padding-block: var(--r5); } }
  .banniere h1 { color: var(--bleu-nuit); }
  .banniere .sous { color: #6B4E08; font-weight: 600; margin-top: .35rem; }

  .banniere-bleue { background: var(--panneau); color: var(--blanc); }
  .banniere-bleue h1 { color: var(--jaune); }
  .banniere-bleue .sous { color: #B9C0DE; }

  /* ══════════════════════════════ LA SCÈNE — toute photo passe par elle ══
     Le corpus du circuit est, et restera, des photos de téléphone prises un
     dimanche : mal cadrées, à contre-jour, avec une poubelle dans le fond. Le
     traitement n'est pas de la décoration, c'est ce qui transforme un
     instantané en image de marque.

     Les cinq arrêts 0 / 26 / 52 / 82 / 100 % et les opacités
     .62 / .14 / .30 / .86 / 1 sont la transcription littérale des
     0x9E / 0x24 / 0x4D / 0xDB / opaque de Scene (lib/widgets/terrain.dart) ;
     le halo doré reprend son centre et son alpha 0x33. Pas d'interprétation :
     une transcription — c'est ce qui rend la loi d'airain vérifiable au lieu
     d'être déclarative.

     .bandeau-photo est l'ALIAS historique : les deux vues qui l'emploient
     n'ont pas une ligne à changer et récupèrent le traitement au passage.

     Chaque dégradé est écrit deux fois : la version rgba() sert de repli aux
     navigateurs sans oklch(), qui ignorent la seconde déclaration. Sans elle
     ils n'auraient AUCUN voile — c'est-à-dire du texte nu sur une photo. */

  .scene, .bandeau-photo {
    --scene-titre: var(--jaune);
    --scene-encre: #DCE3F2;
    position: relative;
    isolation: isolate;
    background: var(--bleu-nuit);
    overflow: hidden;
  }
  :is(.scene, .bandeau-photo) > picture,
  :is(.scene, .bandeau-photo) > img,
  :is(.scene, .bandeau-photo) > picture > img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    object-position: 50% 42%;
    z-index: -3;
  }

  /* La chaleur dorée en haut à droite rattache la photo à la marque ; le
     vignettage referme le cadre sur le sujet. */
  :is(.scene, .bandeau-photo)::before {
    content: "";
    position: absolute; inset: 0; z-index: -2; pointer-events: none;
    background:
      radial-gradient(circle at 78% 8%, rgba(245, 180, 28, .20) 0%, transparent 62%),
      radial-gradient(ellipse 100% 98% at 50% 45%, transparent 34%, rgba(5, 9, 22, .50) 100%);
    background:
      radial-gradient(circle at 78% 8%, oklch(80.9% .162 81.4 / .20) 0%, transparent 62%),
      radial-gradient(ellipse 100% 98% at 50% 45%, transparent 34%, oklch(9% .02 268 / .50) 100%);
  }

  /* Le marine qui monte. Cinq arrêts, pas deux : c'est ce qui permet au titre
     de tenir en bas SANS text-shadow — l'ancien pansement de 24 px de flou
     devient inutile, et la photo reste lisible en son milieu. */
  :is(.scene, .bandeau-photo)::after {
    content: "";
    position: absolute; inset: 0; z-index: -1; pointer-events: none;
    background: linear-gradient(180deg,
      rgba(16, 24, 56, .62)   0%,
      rgba(16, 24, 56, .14)  26%,
      rgba(16, 24, 56, .30)  52%,
      rgba(16, 24, 56, .86)  82%,
      rgba(16, 24, 56, 1)   100%);
    background: linear-gradient(180deg,
      oklch(18.3% .05 270 / .62)   0%,
      oklch(18.3% .05 270 / .14)  26%,
      oklch(18.3% .05 270 / .30)  52%,
      oklch(18.3% .05 270 / .86)  82%,
      oklch(18.3% .05 270 / 1)   100%);
  }

  :is(.scene, .bandeau-photo) .enveloppe {
    position: relative;
    padding-block: clamp(var(--r6), 7vw, var(--r8));
    min-height: clamp(220px, 26vw, 320px);
    display: flex; flex-direction: column; justify-content: flex-end;
  }
  :is(.scene, .bandeau-photo) h1 { color: var(--scene-titre); }
  :is(.scene, .bandeau-photo) .sous { color: var(--scene-encre); max-width: 46ch; }

  @media (max-width: 640px) {
    :is(.scene, .bandeau-photo) .enveloppe { min-height: 260px; }
  }

  /* — PLEIN CAGNARD : la même architecture, RETOURNÉE —
     Le voile n'est plus marine mais ivoire, et il monte du bas comme la
     lumière rasante de 13 h sur du gravier clair.
     Deux portes d'entrée, et une seule raison : une règle @media ne se combine
     pas avec un sélecteur d'attribut, il faut donc écrire le cas « choix
     explicite » et le cas « aucun choix, téléphone réglé en clair ». Sans le
     second, un joueur en Auto garderait une photo de nuit sur une page de
     jour.
     Le surtitre reçoit le bronze : deux vues l'écrivent encore en or par un
     style EN LIGNE (style="color:var(--jaune)"), qu'aucune règle ne peut
     surcharger — mais qui lit --jaune sur l'élément lui-même. On redéfinit
     donc le jeton LÀ, et seulement là : 1,66:1 devient 5,08:1 sans toucher
     une vue, et sans changer l'or des boutons posés sur la même scène. */
  @media (prefers-color-scheme: light) {
    :root:not([data-theme="buvette"]):not([data-theme="sombre"]) :is(.scene, .bandeau-photo) {
      --scene-titre: var(--marine-900);
      --scene-encre: var(--marine-800);
    }
    :root:not([data-theme="buvette"]):not([data-theme="sombre"]) :is(.scene, .bandeau-photo) .surtitre {
      --jaune: var(--or-800);
      color: var(--or-800);
    }
    :root:not([data-theme="buvette"]):not([data-theme="sombre"]) :is(.scene, .bandeau-photo)::after {
      background: linear-gradient(180deg,
        rgba(250, 243, 227, .10)   0%,
        rgba(250, 243, 227, .04)  30%,
        rgba(250, 243, 227, .38)  62%,
        rgba(250, 243, 227, .92)  86%,
        rgba(250, 243, 227, 1)   100%);
      background: linear-gradient(180deg,
        oklch(96.5% .022 87 / .10)   0%,
        oklch(96.5% .022 87 / .04)  30%,
        oklch(96.5% .022 87 / .38)  62%,
        oklch(96.5% .022 87 / .92)  86%,
        oklch(96.5% .022 87 / 1)   100%);
    }
  }

  :root[data-theme="cagnard"] :is(.scene, .bandeau-photo),
  :root[data-theme="clair"]   :is(.scene, .bandeau-photo) {
    --scene-titre: var(--marine-900);
    --scene-encre: var(--marine-800);
  }
  :root[data-theme="cagnard"] :is(.scene, .bandeau-photo) .surtitre,
  :root[data-theme="clair"]   :is(.scene, .bandeau-photo) .surtitre {
    --jaune: var(--or-800);
    color: var(--or-800);
  }
  :root[data-theme="cagnard"] :is(.scene, .bandeau-photo)::after,
  :root[data-theme="clair"]   :is(.scene, .bandeau-photo)::after {
    background: linear-gradient(180deg,
      rgba(250, 243, 227, .10)   0%,
      rgba(250, 243, 227, .04)  30%,
      rgba(250, 243, 227, .38)  62%,
      rgba(250, 243, 227, .92)  86%,
      rgba(250, 243, 227, 1)   100%);
    background: linear-gradient(180deg,
      oklch(96.5% .022 87 / .10)   0%,
      oklch(96.5% .022 87 / .04)  30%,
      oklch(96.5% .022 87 / .38)  62%,
      oklch(96.5% .022 87 / .92)  86%,
      oklch(96.5% .022 87 / 1)   100%);
  }

  /* — carte « prochaine journee » — */

  .prochaine { background: var(--panneau); color: var(--blanc); border-radius: var(--arrondi-2); padding: var(--r5); }
  .prochaine .lieu { font-family: var(--titre); font-weight: 900; font-size: clamp(2.4rem, 6vw, 3.6rem); color: var(--jaune); line-height: .9; }
  .prochaine .detail { color: #D3D9EC; font-weight: 600; }
  .prochaine .actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: var(--r4); }

  /* — file de jetons — */

  .file-jetons { display: flex; }
  .file-jetons .jeton { border: 2px solid var(--panneau); }
  .file-jetons .jeton + .jeton { margin-left: -10px; }

  /* — ligne de classement — */

  /* La hiérarchie, tranchée. Le rang et les points faisaient 1,35 rem tous les
     deux, même graisse, même famille, sur la même ligne : deux nombres à
     égalité, l'œil hésite. L'attente du joueur départage — « se trouver en
     cinq secondes : mon rang, mes points ». Le rang gagne, à 2,6 rem
     d'affiche ; les points passent en Archivo tabulaire, donc redeviennent une
     donnée et non une seconde affiche.
     La colonne de tête tient trois chiffres : le circuit compte 453 licenciés,
     un rang à trois chiffres n'est pas un cas d'école. */
  .ligne-classement {
    display: grid; grid-template-columns: 3.9rem auto 1fr auto; align-items: center;
    gap: .75rem; padding: .7rem .9rem;
    background: var(--carte); border: 1px solid var(--trait); border-radius: var(--arrondi);
    text-decoration: none; color: inherit;
  }
  .ligne-classement:hover { border-color: var(--jaune); color: inherit; }
  .ligne-classement .rang {
    position: relative;
    font-family: var(--titre); font-weight: 800;
    font-size: 2.6rem; line-height: .86; letter-spacing: -.02em;
    text-align: center; color: var(--encre);
    font-variant-numeric: tabular-nums;
  }
  /* Le podium se marque par un FILET, pas par une couleur. L'information ne
     repose donc plus sur la seule teinte (WCAG 1.4.1) et le chiffre récupère
     tout son contraste : l'or profond plafonnait à 2,91:1 sur blanc, sur le
     chiffre le plus regardé du produit. */
  .ligne-classement.podium .rang::before {
    content: ""; position: absolute;
    left: -.5rem; top: 50%; transform: translateY(-50%);
    width: 3px; height: 1.6em; border-radius: 2px;
    background: var(--jaune);
  }
  .ligne-classement.moi { background: var(--panneau); border-color: var(--panneau); color: var(--blanc); }
  .ligne-classement.moi .rang { color: var(--jaune); }
  .ligne-classement.moi .sous { color: #B9C0DE; }
  /* Les deux lignes s'empilent. Les vues écrivent des <span>, donc en ligne :
     le nom et le club se touchaient — « Jean DUPONTLAT · 9 journées ». C'est
     le composant qui doit tenir sa mise en page, pas chaque vue. */
  .ligne-classement .nom, .ligne-classement .sous { display: block; }
  .ligne-classement .nom { font-weight: 700; line-height: 1.2; }
  .ligne-classement .sous { font-size: .78rem; color: var(--encre-2); }
  .ligne-classement .score { text-align: right; }
  .ligne-classement .score .pts {
    font-family: var(--texte); font-weight: 700; font-size: 1.15rem;
    font-variant-numeric: tabular-nums;
  }
  @media (max-width: 640px) {
    .ligne-classement { grid-template-columns: 3.2rem auto 1fr auto; gap: .55rem; }
    .ligne-classement .rang { font-size: 2.1rem; }
  }

  /* — journee d'agenda — */

  .journee {
    display: grid; grid-template-columns: 3.4rem 1fr auto; align-items: center; gap: var(--r3);
    padding: .8rem; background: var(--carte);
    border: 1px solid var(--trait); border-radius: var(--arrondi);
    text-decoration: none; color: inherit;
  }
  .journee:hover { border-color: var(--jaune); color: inherit; }
  .journee .date { text-align: center; }
  .journee .date .j, .journee .date .s { display: block; }
  .journee .date .j { font-family: var(--titre); font-weight: 900; font-size: 1.6rem; line-height: .9; color: var(--bleu-texte); }
  .journee .date .s { font-size: .62rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--encre-2); }
  .journee .titre { font-weight: 700; line-height: 1.25; }
  .journee .info  { font-size: .82rem; color: var(--encre-2); }
  .journee.finale { background: var(--jaune); border-color: var(--jaune-profond); }
  .journee.finale .date .j, .journee.finale .titre { color: var(--bleu-nuit); }
  .journee.finale .date .s, .journee.finale .info { color: #6B4E08; }
  .journee.passee { opacity: .72; }

  /* — rencontre — */

  .rencontre {
    display: grid; grid-template-columns: 2.6rem 1fr auto 1fr; align-items: center; gap: var(--r3);
    padding: .7rem .8rem; border: 1px solid var(--trait); border-radius: var(--arrondi);
    background: var(--carte);
  }
  .rencontre .jeu { font-family: var(--titre); font-weight: 900; font-size: 1.2rem; text-align: center; color: var(--bleu-texte); }
  .rencontre .equipe { font-weight: 600; font-size: .92rem; line-height: 1.25; min-width: 0; }
  .rencontre .equipe .num { color: var(--encre-2); font-size: .76rem; font-weight: 700; }
  .rencontre .score { font-family: var(--titre); font-weight: 900; font-size: 1.5rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
  .rencontre .score .perdant { color: var(--encre-2); }
  .rencontre.a-jouer { border-style: dashed; }
  .rencontre.exempte { background: var(--jaune-voile); grid-template-columns: 2.6rem 1fr auto; }
  @media (max-width: 640px) {
    .rencontre { grid-template-columns: 2.4rem 1fr; grid-template-areas: "jeu a" "score b"; row-gap: .3rem; }
    .rencontre .jeu { grid-area: jeu; }
    .rencontre .score { grid-area: score; font-size: 1.2rem; text-align: center; }
    .rencontre .equipe:first-of-type { grid-area: a; }
    .rencontre .equipe:last-of-type  { grid-area: b; }
  }

  /* — nuee de noms : chaque joueur est une cible tactile confortable — */

  .nuee { display: flex; flex-wrap: wrap; gap: .4rem; }
  .nom-joueur {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .3rem .6rem .3rem .3rem; min-height: 44px; /* cible tactile */
    background: var(--carte); border: 1px solid var(--trait); border-radius: 999px;
    color: var(--encre); text-decoration: none;
    font-size: .82rem; font-weight: 600; white-space: nowrap;
  }
  .nom-joueur:hover { border-color: var(--jaune); color: var(--encre); }

  /* — annonce — */

  .annonce { border-left: 4px solid var(--bleu); }
  .annonce.urgent { border-left-color: var(--rouge); }
  .annonce.resultat { border-left-color: var(--vert); }
  .annonce.convocation { border-left-color: var(--jaune); }
  .annonce .titre { font-weight: 700; line-height: 1.25; margin-top: .35rem; }

  /* — statistiques — */

  .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 1px; background: var(--trait); border: 1px solid var(--trait); border-radius: var(--arrondi); overflow: hidden; }
  .stats > div { background: var(--carte); padding: .9rem .6rem; text-align: center; }
  .stats .chiffre { color: var(--bleu-texte); }

  /* — erreur — */

  .erreur-page { min-height: 60dvh; display: grid; place-items: center; text-align: center; }
  .erreur-page .code { font-family: var(--titre); font-weight: 900; font-size: clamp(5rem, 18vw, 10rem); color: var(--jaune); line-height: .8; }

  /* — acces au compte : connexion, activation, mot de passe oublie —
     Ces pages se remplissent au telephone, souvent sans lunettes et parfois
     en plein soleil sur un terrain. D'ou des cibles de 48 px et un champ de
     code demesure : ici la lisibilite passe avant la sobriete. */

  /* Le marine sur l'abysse ne se lit pas. Les trois règles de bascule qu'il
     fallait tenir ici sont remplacées par le jeton --lien, qui porte déjà les
     deux thèmes — et qui, lui, ne se trompe pas quand un joueur choisit
     Plein Cagnard sur un téléphone réglé en sombre. */
  .acces h1 { color: var(--lien); }

  .acces .champ input { min-height: 48px; font-size: 1rem; }
  .acces .bouton { min-height: 48px; }

  .acces .etapes {
    font-size: .72rem; font-weight: 800; letter-spacing: .18em;
    text-transform: uppercase; color: var(--encre-2);
  }

  .acces .code-six {
    font-size: 1.8rem; font-weight: 800; letter-spacing: .45em;
    text-align: center; text-indent: .45em; min-height: 60px;
    font-variant-numeric: tabular-nums;
  }

  .acces .facultatif { font-weight: 600; text-transform: none; letter-spacing: 0; }
  .acces .prenom { color: var(--encre); font-weight: 700; }

  /* Trois issues, aucune enfouie : chacune debloque un cas different. */
  .acces .issues { border-top: 1px solid var(--trait); padding-top: var(--r4); }
  .acces .issues a { display: block; padding: .55rem 0; font-weight: 600; }

  /* Case « afficher le mot de passe », posee par acces.js. */
  .acces .voir-mdp { margin-top: calc(var(--r2) * -1); }
  .acces .voir-mdp input { width: 22px; height: 22px; accent-color: var(--lien); }
}

/* ──────────────────────────────────────────────────────────── UTILITAIRES ── */

@layer utilitaires {
  .muet     { color: var(--encre-2); }
  .petit    { font-size: .85rem; }
  .minus    { font-size: .78rem; }
  .gras     { font-weight: 700; }
  .tres-gras { font-weight: 800; }
  .centre   { text-align: center; }
  .droite   { text-align: right; }
  .nombres  { font-variant-numeric: tabular-nums; }
  .jaune    { color: var(--jaune-profond); }
  .bleu     { color: var(--bleu-texte); }
  .vert     { color: var(--vert); }
  .rouge    { color: var(--rouge); }
  .plein    { width: 100%; }
  .rien     { display: none; }
  .coupe    { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  .mt-2 { margin-top: var(--r2); }  .mt-4 { margin-top: var(--r4); }
  .mt-5 { margin-top: var(--r5); }  .mt-6 { margin-top: var(--r6); }
  .mb-2 { margin-bottom: var(--r2); } .mb-4 { margin-bottom: var(--r4); }

  .invisible {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }

  @media print {
    .entete, .pied, .nav, .bouton, .bascule-nav { display: none !important; }
    body { background: #fff; color: #000; }
    .carte { border-color: #999; box-shadow: none; }
  }
}
