/* Northern Moose — système de design (porté du prototype, qualité Linear/Stripe). */
/* Toutes les couleurs de l'application passent par ces jetons : plus une seule
   n'est écrite en dur dans les règles. C'est ce qui permet au thème « nuit » de
   basculer l'ensemble sans toucher au reste de la feuille. */
:root{
  /* Surfaces */
  --bg:#f6f5f2; --panel:#ffffff; --panel2:#faf9f5; --panel3:#f5f3ee;
  --line:#ebe8e1; --line2:#e0ddd4; --hover:#f1efe9; --topbar-bg:#ffffffee;
  /* Encres. --mut2 avait été « durci pour le contraste AA » mais calibré sur le SEUL
     --panel : il y donnait 4,51:1 pour un seuil de 4,50, et tombait partout ailleurs
     (4,01 sur --accent-soft, 4,06 sur --tag-bg, 4,07 sur --panel3, 4,13 sur --bg).
     Mesuré : 15 paires jour sous le seuil, contre 2 la nuit. Il vise maintenant 4,65
     au pire cas, sur les HUIT surfaces où il se pose, et non plus la plus favorable.

     --mut3 est DÉCORATIF, et cette fois pour de vrai : il ne rend plus aucun texte,
     seulement les traits du filigrane de patinoire. Il habillait `.t-hint`, les
     libellés de KPI et `.encre-effacee`, soit du texte de 10 à 12 px à 2,87:1. */
  --ink:#1a201d; --ink2:#2b332e; --sub:#3f4a44; --mut:#5f6b64; --mut2:#656e69; --mut3:#8a948e;
  /* Accents. La sémantique ne bouge jamais : vert = buts pour, rouge = buts contre. */
  --green:#2f7d56; --green-d:#1f6b45; --green-dd:#1f3a2c; --green-mint:#7fd3a6;
  --green-h:#1a5d3c; --on-green:#eaf7ef;
  /* --green-strong : encre verte forte (elle s eclaircit la nuit).
     --on-green-dd : encre posee sur --green-dd, qui reste sombre dans les deux thèmes. */
  --green-strong:#1f3a2c; --on-green-dd:#eaf7ef;
  /* --gold : la variante nuit avait été éclaircie pour son fond, pas la variante
     jour, qui ne tenait que 3,5:1 sur --gold-bg. Elle est foncée à son tour. */
  /* --red-d passe de #b8442c a #b2422b : il valait 4,48:1 sur --ag-bg, ou il rend
     les podiums, la cellule +/- surlignee et le badge « A corriger ». 4,72 desormais. */
  --red:#c0492f; --red-d:#b2422b; --blue:#2a6fae; --gold:#7a6015;
  /* Séries de graphique au-delà des quatre accents. Les histogrammes de gardiens
     empruntent vert, bleu, doré et rouge, puis butaient dès le cinquième gardien
     adverse de la saison. Mêmes contraintes que les quatre autres : au-dessus de
     4,5:1 sur --panel, et une teinte que les autres n'occupent pas. */
  --serie5:#7b6bb5; --serie6:#4f7778;
  /* Fonds de camp et fonds neutres */
  --for-bg:#e7f1ea; --for-bg2:#f3f8f4; --for-line:#bcdcc9;
  --ag-bg:#f7e7e2;  --ag-bg2:#faf2ef;  --ag-line:#eccdc2; --ag-line2:#ddb8ab;
  --accent-soft:#eef3ef; --neutral-bg:#eef0ec; --track:#eef0ec; --tag-bg:#f4f3ef;
  /* Jetons venus des gabarits (croquis de patinoire, filet, aide, podiums) */
  --for-soft:#d6e6dc;
  --green-txt:#4a6a57; --green-txt2:#1f5236;
  --line3:#d8d3c8; --rink-bg:#edece7; --rink-line:#b49d92; --rink-mut:#8a97a8;
  --gold-bg:#f6efdc; --blue-bg:#f2f6fa;
  /* Détails */
  --avatar-bg:#e4e9e4; --avatar-ink:#3f5a4b; --barre:#cfcabd; --fleche:#b3a9a0;
  --logo-bg:#111; --ombre:26,32,29;
  --green-glow:rgba(31,107,69,.3); --green-glow2:rgba(31,107,69,.12);
  /* Lueur des bords de tableau défilant : sombre le jour, claire la nuit. */
  --scroll-cue:rgba(26,32,29,.14);
  /* Mouvement — une seule courbe, une durée, pour toute l'application. */
  --t:.24s cubic-bezier(.2,.7,.2,1);
  /* La colonne de contenu, partagée par la page et par la barre du haut. */
  /* Le retrait du contenu depuis le bord de sa colonne. UNE seule valeur, parce que
     trois conteneurs doivent tomber sur la même verticale : la page, la barre du haut
     et le bandeau des nouveautés. Elle valait 30 dans `.page` et 26 dans les deux
     autres, et ce décalage de 4 px se voyait entre le logo et le titre de page. */
  --marge-page:30px;
  /* La même marge, pour un bandeau PLEINE LARGEUR : il faut d'abord rattraper la
     moitié de ce qui sépare la fenêtre de la colonne, sinon son contenu file au bord
     de l'écran pendant que celui de la page reste au centre.

     À n'employer QUE sur un conteneur pleine largeur. Sur un conteneur qui porte déjà
     `max-width:var(--colonne); margin:0 auto`, la compensation s'ajoute à son propre
     centrage : le bandeau des nouveautés s'enfonçait ainsi de 96 px de plus que la
     page à 2000 px de large, et de 4 px en dessous de 1400.

     `100%` et NON `100vw` : le pourcentage se résout sur le bloc conteneur, alors que
     l'unité de fenêtre compte la barre de défilement. Le centrage de la page, lui, se
     fait sur le conteneur. Les deux s'écartaient donc de la largeur de cette barre —
     5 px mesurés à 1990 px de large, entre le logo et le titre de la page. */
  --marge-colonne:calc(max(0px, 100% - var(--colonne)) / 2 + var(--marge-page));
  --colonne:clamp(1400px, 90vw, 1900px);
  --sans:'Hanken Grotesk',-apple-system,BlinkMacSystemFont,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,monospace;

  /* ── L'échelle ────────────────────────────────────────────────────────────
     L'ensemble FERMÉ des valeurs autorisées. Ce qui rendait cette application
     incohérente n'était pas la duplication mais l'échelle : 24 tailles de
     police et 26 valeurs d'espacement, le même objet existant à cinq tailles.
     Les crans ne sont pas choisis a priori, ils sont relevés sur les valeurs
     réellement employées — la table de report complète est dans
     docs/ECHELLE.md, regénérable par `manage.py nm_inventaire --echelle`.
     Reporter tout le projet dessus coûte au plus 1 px en typographie et 2 px
     en espacement, exceptions nommées exclues.

     Ces jetons ne dépendent PAS du thème : ils vivent ici seulement, jamais
     redéclarés dans le bloc nuit, contrairement aux couleurs. */
  --e1:4px; --e2:6px; --e3:8px; --e4:11px;
  --e5:14px; --e6:18px; --e7:24px; --e8:32px;

  /* Texte. Ancrée sur 13,5 px, la taille du corps de tableau, donc le texte le
     plus lu de l'application. Deux crans sont sciemment ABSENTS, et c'est ce
     qui en fait un rythme plutôt qu'une liste : 14 px, parce que le corps de
     tableau descend à 13,5 (il RÉTRÉCIT, aucun risque de débordement), et
     13 px, parce que les tables denses descendent à 12,5 et gardent ainsi leur
     cran d'écart avec les autres. Rien sous 10,5 px ne porte de donnée. */
  --tx-1:10.5px; --tx-2:11.5px; --tx-3:12.5px; --tx-4:13.5px;
  --tx-5:15px;   --tx-6:17px;   --tx-7:20px;   --tx-8:28px;

  /* Chiffres affichés, en chasse fixe. Échelle séparée parce que la mono n'a
     pas les mêmes métriques : au-dessus de 17 px, un cran de texte n'y
     correspond à rien. Le cran de 24 px n'était pas dans le premier jet, qui
     sautait de 19 à 27 et laissait sans cran les compteurs d'import et les
     totaux de match, à 23, 24 et 25 px. */
  --num-1:19px; --num-2:24px; --num-3:28px; --num-4:40px;

  /* Graisses nommées : `600` ne dit pas ce qu'il fait, `--gr-app` si. */
  --gr-cour:500; --gr-app:600; --gr-fort:700; --gr-titre:800;

  /* ── Les rôles de densité ─────────────────────────────────────────────────
     Un cran ne dit pas quand l'employer. Ces jetons nomment les décisions de
     densité de l'application, et c'est le SEUL endroit où on les arbitre.
     Deux rythmes, mesurés et non supposés : entre les blocs d'une page et
     entre les cartes, l'écart dominant est 14 px ; à l'intérieur d'une carte,
     les 62 lignes de détail des journaux tiennent en 10 px, et montent au cran
     de 11. Confondre les deux donnait 4 px de trop dans chaque ligne de but. */
  --gouttiere:var(--e5); --gouttiere-x:var(--e5);
  --pile:var(--e4); --pile-serree:var(--e2);
  --mesure-prose:78ch;
  /* La mesure d'un formulaire. Elle était écrite TROIS fois avec trois valeurs — 640
     ici, 620 en dur dans `goal_log.html`, 680 dans `game_form.html` — et le palier
     large en avait inventé une quatrième. Une seule déclaration, et tout le reste s'en
     dérive. */
  --mesure-formulaire:640px;

  /* Rembourrage des cartes. Les 53 cartes en portaient DIX formes différentes,
     parce que `.card` n'en définissait aucune. Quatre registres les absorbent,
     et tous partagent 18 px d'horizontal : seul le vertical distingue une carte
     de texte d'une carte de chiffres. Les cartes à 22 px d'horizontal y perdent
     4 px, au-delà du budget de 2 px du report — l'écart est assumé ici plutôt
     que payé par un cinquième registre qui n'aurait servi qu'à l'éviter. */
  --pad-carte:var(--e5) var(--e6);         /* 14 18 — le registre courant */
  --pad-carte-aere:var(--e6) var(--e6);    /* 18 18 — texte et formulaires */
  --pad-carte-dense:var(--e4) var(--e5);   /* 11 14 — cartes de distribution */
  --pad-carte-table:var(--e3) var(--e2);   /* 8 6  — cartes à table défilante */

  /* La ligne « libellé · jauge · n · % ». Elle existait à six largeurs de
     libellé, quatre hauteurs de barre et six largeurs de colonne : sur une
     bande de trois cartes, les trois pistes ne commençaient pas au même x. */
  --h-barre:var(--e2); --lbl:150px; --col-n:20px; --col-p:32px;

  /* Hauteurs de conteneur de graphique. Six valeurs écrites à la main dans les
     gabarits, dont un ruban de 230 px de haut dans une carte qui en fait 1628
     de large à 1920 : un rapport de 7 pour 1 sur une courbe qui porte une
     vingtaine de joueurs. */
  --h-graphe:190px; --h-graphe-court:110px; --h-graphe-large:230px;
}

/* ── L'écran de référence ───────────────────────────────────────────────────
   Le seul palier dépendant de la largeur de tout le chantier. Il s'ouvre
   AU-DESSUS de tous les points de rupture existants — ce sont des `max-width`,
   la plus haute étant 1100 — donc il ne peut ni les recouvrir, ni les
   réordonner, ni les éditer. Le comportement mobile reste intact, et un test
   vérifie qu'aucune `min-width` ne redescend dans son domaine.

   Il ne réécrit AUCUNE règle : il réassigne quatre jetons de rôle.

   Seul l'espace HORIZONTAL respire. La typographie ne varie jamais avec la
   largeur — c'est ce qui fait qu'une capture à 1920 et une à 1280 se lisent
   comme la même application — et la gouttière verticale non plus, parce que
   1920 est aussi 1080 : on n'a qu'environ 950 px utiles sous la barre collante,
   et le Tableau des buts fait déjà six écrans de défilement. */
@media (min-width:1600px){
  :root{
    --gouttiere-x:var(--e6);            /* 14 → 18 : les colonnes se séparent */
    --pad-carte:var(--e5) var(--e7);    /* latéral seul, la hauteur ne bouge pas */
    --lbl:170px;                        /* la piste de jauge respire */
    --h-graphe-large:300px;             /* le comparatif cesse d'être un ruban */
  }
}

/* Thème nuit : la glace de la page d'accueil, portée sur l'application. Seuls
   les jetons changent ; aucune règle plus bas n'a conscience du thème. */
:root[data-theme="nuit"]{
  --bg:#07120e; --panel:#101f19; --panel2:#0d1a15; --panel3:#132620;
  --line:#1e332a; --line2:#274036; --hover:#16291f; --topbar-bg:#0a1611ee;
  --ink:#eaf7ef; --ink2:#d7e9df; --sub:#b6d0c2; --mut:#9bb8a8; --mut2:#8fae9d; --mut3:#7a9989;
  --green:#4fae7c; --green-d:#5cbd89; --green-dd:#0f2a1f; --green-mint:#8fd0ae;
  --green-h:#6bcf9a; --on-green:#06140e;
  --green-strong:#a5dcc0; --on-green-dd:#eaf7ef;
  --red:#e0705a; --red-d:#e88a76; --blue:#6aa8dd; --gold:#d3b45f;
  --serie5:#9b8ed0; --serie6:#7fb0b1;
  --for-bg:#12301f; --for-bg2:#0e2419; --for-line:#265f3f;
  --ag-bg:#3a1d17;  --ag-bg2:#2a1611;  --ag-line:#6b3327; --ag-line2:#8a4234;
  --accent-soft:#132e21; --neutral-bg:#17251f; --track:#1b2c24; --tag-bg:#152521;
  --for-soft:#1c4630;
  --green-txt:#8fd0ae; --green-txt2:#a5dcc0;
  --line3:#2a4438; --rink-bg:#183028; --rink-line:#416750; --rink-mut:#6d8a9b;
  --gold-bg:#2b2415; --blue-bg:#14232e;
  --avatar-bg:#1c3327; --avatar-ink:#9fd7b8; --barre:#263d32; --fleche:#7a9989;
  --logo-bg:#04100b; --ombre:0,0,0;
  --scroll-cue:rgba(234,247,239,.22);
  --green-glow:rgba(127,211,166,.28); --green-glow2:rgba(127,211,166,.18);
  color-scheme:dark;
}
*{ box-sizing:border-box; }
body{ margin:0; background:var(--bg); color:var(--ink); font-family:var(--sans); }
::-webkit-scrollbar{ width:10px; height:10px; }
::-webkit-scrollbar-thumb{ background:var(--barre); border-radius:8px; border:2px solid var(--bg); }
a{ text-decoration:none; color:inherit; }
/* Alpine EXIGE cette règle : `x-cloak` ne fait rien tout seul, c'est un attribut
   qu'Alpine retire une fois le composant monté. Sans elle, tout panneau `x-show`
   est visible le temps qu'Alpine passe.

   Elle ne suffit pas pour autant. Sur un élément qui porte un `id`, htmx rétablit
   après l'échange les attributs venus du serveur et efface le `display:none`
   qu'Alpine a posé — donc APRÈS le retrait de x-cloak, hors de portée de cette
   règle. Ces panneaux portent en plus un masque en ligne ; voir base.html. */
[x-cloak]{ display:none !important; }
.mono{ font-family:var(--mono); }
@keyframes fadeIn{ from{ opacity:0; } to{ opacity:1; } }
@keyframes pop{ 0%{ transform:scale(.96); opacity:.4 } 100%{ transform:scale(1); opacity:1 } }
@keyframes flash{ 0%{ background:var(--for-bg) } 100%{ background:transparent } }

/* Patinoire en filigrane, sous toute l'application ---------------------------
   Fixe : elle ne défile pas, donc elle ne tire jamais l'œil pendant qu'on lit un
   tableau. Les cartes étant opaques, elle n'apparaît que dans les marges. Elle
   se renforce en mode nuit, où le fond est assez sombre pour l'accueillir. */
.rk-fond{ position:fixed; inset:0; width:100%; height:100%; z-index:-1;
  pointer-events:none; opacity:.5; }
.rk-fond *{ fill:none; vector-effect:non-scaling-stroke; }
.rk-fond .rk-bande{ stroke:var(--line2); stroke-width:1.5; }
.rk-fond .rk-but, .rk-fond .rk-cercle{ stroke:var(--red); stroke-width:1; opacity:.22; }
.rk-fond .rk-bleue{ stroke:var(--blue); stroke-width:5; opacity:.10; }
.rk-fond .rk-rouge{ stroke:var(--red); stroke-width:5; opacity:.12; }
.rk-fond .rk-zone{ stroke:var(--blue); stroke-width:1; opacity:.22; }
.rk-fond .rk-filet{ stroke:var(--mut3); stroke-width:1; opacity:.3; }
.rk-fond .rk-point{ fill:var(--red); opacity:.18; }
:root[data-theme="nuit"] .rk-fond{ opacity:.85; }

/* Pages de référence (Aide, Données, fiche d'un compte) : elles occupent la même
   colonne que la Synthèse et les sept autres pages de données. Elles étaient
   plafonnées à 1240 et 1040 px au nom de la lisibilité, mais le plafond se voyait :
   deux pages plus étroites que les huit autres, dans le même menu.

   La lisibilité est préservée là où elle se joue vraiment, c'est-à-dire sur la
   longueur de ligne du texte courant, plafonnée à --mesure-prose. Un conteneur
   large ne nuit pas ; une ligne de 1200 px, oui. Les cartes et les tableaux, eux,
   remplissent la colonne : c'est ce qu'on leur demande. */
.page-texte{ max-width:none; margin:0 auto; }
.page-texte p, .page-texte .h-sub{ max-width:var(--mesure-prose); }
/* Deux cartes côte à côte dont les contenus n'ont pas la même longueur. La rangée
   les met déjà à la même hauteur ; ce modificateur amène en plus l'action au bas de
   chacune, pour que les deux boutons tombent sur la même ligne au lieu de flotter
   chacun à la fin de son texte. Ce n'est pas un utilitaire : la couche exclut
   volontairement les familles de mise en page, et une hauteur partagée est une
   décision de composant. */
.card.colonne{ display:flex; flex-direction:column; }
.card.colonne .pousse-bas{ margin-top:auto; }
/* La phrase d'explication et le premier libellé de champ se touchaient, à zéro pixel,
   sur les deux cartes de la page d'un compte à 375 px et sur la plus haute des deux à
   1500 px. La prose et le libellé se lisaient alors comme un seul bloc de texte, et le
   libellé perdait son statut de libellé.

   Trois choses concouraient : `.h-sub` ne déclare aucune marge, `.pile` n'espace
   qu'ENTRE ses enfants et jamais avant le premier, et le `margin-top:auto` ci-dessus
   vaut zéro dès que la carte n'a plus de mou. Un `gap` sur `.card.colonne` serait le
   mauvais correctif : il s'ajouterait au `margin-bottom` de `.entete-carte` et
   casserait le zéro volontaire entre un titre et son sous-titre sur la page Données. */
.card.colonne > .h-sub{ margin-bottom:var(--pile); }

/* Page Effectif : l'équipe posée sur la glace -------------------------------
   La patinoire prend toute la colonne : coincée dans une demi-largeur, elle
   n'avait plus l'air d'un stade. La fiche du joueur passe dessous. */
/* Deux colonnes : la patinoire à gauche, la fiche du joueur à droite, à hauteur
   égale. minmax(0, …) et non 1fr sec : une piste en 16/9 a une largeur préférée
   énorme, et une colonne « auto » s'y ajusterait en faisant déborder la page. */
.eff-pile{ display:grid; grid-template-columns:minmax(0, 1.25fr) minmax(0, 1fr); gap:var(--e5); }
@media (max-width:1100px){ .eff-pile{ grid-template-columns:minmax(0, 1fr); } }
/* La glace de nuit valait #0e1c17 contre une carte #101f19, soit 1,03:1 : la patinoire
   de cette page n'existait tout simplement pas en ambiance nuit. L'assombrir ne pouvait
   pas la sauver, même le noir pur ne donne que 1,23 contre cette carte : il fallait
   l'éclaircir. Elle est à 1,21, et les traits ont suivi pour rester lisibles SUR elle,
   de 1,60 à 2,20.

   La mesure a révélé au passage ce que l'audit n'avait pas vu : le thème JOUR avait le
   même défaut en plus discret, ses traits à 1,62 sur la glace, soit presque le chiffre
   de la nuit avant correction. Le croquis était donc pâle dans les deux ambiances, pas
   dans une. Le trait de jour passe à 2,17. Les deux jetons ne servent que cette page. */
.eff-glace{ position:relative; margin-top:16px; padding:26px 20px 28px; border-radius:14px;
  border:1px solid var(--line); background:var(--rink-bg); overflow:hidden;
  /* Les groupes s'étalent sur la glace au lieu de se masser au centre :
     les attaquants dans le tiers haut, les défenseurs dans le tiers bas. */
  display:grid; gap:var(--e7); align-content:space-evenly;
  width:100%; }
/* Une hauteur plancher, et rien de plus. Avant : `aspect-ratio:16/9` avec
   `max-height:760px`, donc une hauteur qui ne dépendait que de la largeur —
   dès que les attaquants tenaient sur trois rangées, la rangée des gardiens
   passait sous le bord et `overflow:hidden` la tranchait sans rien signaler. */
@media (min-width:900px){
  .eff-glace{ min-height:420px; }
}
/* Le croquis est en « meet » et non « slice » (effectif.html) : il entre EN ENTIER
   dans le cadre, quelle que soit la hauteur que le contenu impose. « slice »
   remplissait le cadre en se laissant rogner, et comme le cadre est plus large que
   16/9, c'étaient les bandes hautes et basses qui disparaissaient : une patinoire
   sans bord. Le prix est une marge latérale sur un cadre très large, et c'est le
   seul réglage qui ne coupe jamais rien. */
.eff-rink{ position:absolute; inset:0; width:100%; height:100%; opacity:.5; pointer-events:none; }
.eff-rink *{ fill:none; vector-effect:non-scaling-stroke; }
.eff-rink .rk-bande{ stroke:var(--rink-line); stroke-width:1.5; }
.eff-rink .rk-bleue{ stroke:var(--blue); stroke-width:6; opacity:.16; }
.eff-rink .rk-rouge{ stroke:var(--red); stroke-width:6; opacity:.18; }
.eff-rink .rk-cercle, .eff-rink .rk-but{ stroke:var(--red); stroke-width:1.2; opacity:.28; }
.eff-rink .rk-zone{ stroke:var(--blue); stroke-width:1.2; opacity:.3; }
.eff-rink .rk-filet{ stroke:var(--rink-mut); stroke-width:1.2; opacity:.5; }
.eff-rink .rk-point{ fill:var(--red); opacity:.25; }

.eff-zone{ position:relative; }
.eff-lbl{ font:var(--gr-fort) var(--tx-2) var(--sans); letter-spacing:.12em; text-transform:uppercase;
  color:var(--mut2); margin-bottom:9px; text-align:center; }
/* --mut2 a été calibré sur --panel ; la glace est plus sombre et le rapport y
   tombait à 3,8:1. Le banc, lui, reste sur --panel et garde --mut2. */
.eff-glace .eff-lbl{ color:var(--mut); }
.eff-rang{ display:flex; flex-wrap:wrap; gap:var(--e3); justify-content:center; }

.eff-chip{ display:inline-flex; align-items:center; gap:var(--e3); min-height:44px;
  padding:var(--e2) var(--e5) var(--e2) var(--e2); border-radius:999px; cursor:pointer;
  border:1px solid var(--line2); background:var(--panel); color:var(--ink2);
  font:var(--gr-app) var(--tx-4) var(--sans);
  transition:transform var(--t), border-color var(--t), box-shadow var(--t); }
.eff-chip .n{ display:grid; place-items:center; min-width:32px; height:32px; border-radius:50%;
  background:var(--neutral-bg); font:var(--gr-fort) var(--tx-4) var(--mono); color:var(--ink); }
.eff-chip .nom{ white-space:nowrap; }
.eff-chip .pm{ font:var(--gr-fort) var(--tx-2) var(--mono); color:var(--mut); }
.eff-chip:hover{ transform:translateY(-2px); border-color:var(--green);
  box-shadow:0 10px 22px -14px rgba(var(--ombre),.55); }
.eff-chip:active{ transform:none; }
.eff-chip.pos{ background:var(--for-bg); border-color:var(--for-line); }
.eff-chip.pos .n{ background:var(--panel); color:var(--green-strong); }
.eff-chip.pos .pm{ color:var(--green-d); }
.eff-chip.neg{ background:var(--ag-bg); border-color:var(--ag-line); }
.eff-chip.neg .n{ background:var(--panel); color:var(--red-d); }
.eff-chip.neg .pm{ color:var(--red-d); }

.eff-num{ display:grid; place-items:center; width:46px; height:46px; border-radius:14px;
  background:linear-gradient(150deg, var(--green), var(--green-dd));
  color:var(--on-green-dd); font:var(--gr-fort) var(--num-1) var(--mono); letter-spacing:-.04em; flex:none; }

/* La fiche partage la hauteur de la patinoire : sinon elle flottait en moignon
   de 124 px à côté d'une carte de 443, avec un grand vide sous les deux.
   L'état vide se centre tout seul ; une fiche remplie coule depuis le haut. */
.eff-fiche{ display:flex; flex-direction:column; }
/* Le rythme de la fiche est HIÉRARCHIQUE, et c'est pour ça qu'il est resté en
   ligne jusqu'ici : un intitulé de section s'écarte de ce qui précède plus
   qu'un bloc de chiffres ne s'écarte du sien. Un hibou uniforme l'aurait
   aplati et aurait cassé le groupement visuel — le libellé aurait flotté
   entre deux blocs au lieu d'appartenir au suivant. */
.eff-fiche > * + *{ margin-top:var(--pile); }
.eff-fiche > * + .sectionlabel{ margin-top:var(--e7); }
.eff-fiche > .sectionlabel + *{ margin-top:var(--e3); }
.eff-fiche > .empty{ margin:auto; }
/* Le hx-indicator des pastilles vise la fiche elle-même : sans cette règle, htmx
   posait bien .htmx-request dessus mais rien ne l'exploitait, et sur un réseau
   lent la carte gardait les chiffres du joueur précédent, prêts à être attribués
   au joueur qu'on venait de taper. Pas .htmx-indicator ici : elle la rendrait
   invisible au repos. */
.eff-fiche.htmx-request{ opacity:.45; pointer-events:none; transition:opacity .15s ease; }

/* Le banc des affiliés : hors de la glace, et plus discret. */
.eff-banc{ margin-top:14px; padding-top:var(--e5); border-top:1px dashed var(--line2); }
.eff-banc .eff-chip{ background:transparent; border-style:dashed; color:var(--mut); }
.eff-banc .eff-chip .n{ background:var(--neutral-bg); color:var(--mut); }

/* Le nom ne disparaît que sur un vrai petit écran. Il était masqué sous 900 px,
   or c'est exactement là que la carte passe pleine largeur et gagne de la place :
   sur un iPad en portrait, il ne restait que des numéros. */
@media (max-width:430px){
  .eff-chip .nom{ display:none; }
  .eff-chip{ padding-right:10px; }
}
@media (prefers-reduced-motion:reduce){ .eff-chip{ transition:none; } .eff-chip:hover{ transform:none; } }

/* Topbar / nav */
/* Le fond reste pleine largeur, mais le contenu s'aligne sur la colonne de page :
   sans ça, à 2560 px, la marque et les actions filaient aux deux bords de l'écran
   pendant que le contenu restait au centre. */
.topbar{ position:sticky; top:0; z-index:30; display:grid; grid-template-columns:1fr auto 1fr;
  align-items:center; gap:var(--e7);
  padding-block:13px;
  padding-inline:var(--marge-colonne);
  background:var(--topbar-bg); backdrop-filter:blur(8px); border-bottom:1px solid var(--line); }
.logo{ width:32px; height:32px; border-radius:9px; object-fit:cover; display:block;
  background:var(--logo-bg); }
.brand{ font:var(--gr-titre) var(--tx-5) var(--sans); letter-spacing:-.2px; }
.brand-sub{ font:var(--gr-app) var(--tx-2) var(--sans); color:var(--mut2); }
.nav{ display:flex; gap:2px; flex-wrap:wrap; justify-content:center; }
/* ── Le déclencheur du menu de navigation ───────────────────────────────────
   Invisible au-dessus de 900 px, où le ruban d'onglets tient tout seul. Même
   grammaire que le déclencheur du menu du compte, à qui il fait face : c'est le
   vocabulaire que l'application parle déjà, et non un motif importé. */
.nav-declencheur{ display:none; align-items:center; justify-content:center;
  width:44px; min-height:44px; border:1px solid var(--line2); border-radius:12px;
  background:var(--panel); color:var(--ink2); cursor:pointer;
  transition:background var(--t), border-color var(--t); }
.nav-declencheur:hover{ background:var(--hover); border-color:var(--line3); }
/* Les deux icônes se substituent depuis `aria-expanded`, seule source de l'état.
   Elles employaient `x-show` : l'icône de fermeture dépendait donc d'Alpine, comme
   le bouton lui-même. En CSS, elle suit l'attribut que le JS pose, et elle est juste
   même si un script tarde. */
.nav-declencheur .nav-icone-fermer{ display:none; }
.nav-declencheur[aria-expanded="true"] .nav-icone-ouvrir{ display:none; }
.nav-declencheur[aria-expanded="true"] .nav-icone-fermer{ display:inline; }
.nav-declencheur[aria-expanded="true"]{ background:var(--hover); border-color:var(--green); }
.nav-tab{ padding:var(--e3) var(--e4); border-radius:8px; background:transparent; color:var(--mut);
  font:var(--gr-app) var(--tx-4) var(--sans); cursor:pointer; border:none; white-space:nowrap; }
.nav-tab:hover{ background:var(--hover); }
/* --green sur --accent-soft donnait 4,46:1, le seul manquement present sur les TREIZE
   pages puisque la barre est partagee. --green-d y monte a 5,76. */
.nav-tab.on{ background:var(--accent-soft); color:var(--green-d); font-weight:700; }
.avatar{ width:28px; height:28px; border-radius:50%; background:var(--avatar-bg); display:flex;
  align-items:center; justify-content:center; font:var(--gr-fort) var(--tx-3) var(--sans); color:var(--avatar-ink); }
.role-chip{ font:var(--gr-app) var(--tx-2) var(--sans); color:var(--mut2); background:var(--accent-soft); padding:var(--e1) var(--e3); border-radius:20px; }
.topbar-right{ display:flex; align-items:center; gap:var(--e4); justify-self:end; flex-wrap:wrap; justify-content:flex-end; }

/* Le coin de l'utilisateur : profil, rôle, langue, ambiance et sortie tenaient
   côte à côte sans rien qui les relie. Ils forment maintenant un seul bloc. */
.ubloc{ display:flex; align-items:center; gap:var(--e2); padding:var(--e1) var(--e2) var(--e1) var(--e1);
  border:1px solid var(--line); border-radius:999px; background:var(--panel);
  transition:border-color var(--t); }
.ubloc:hover{ border-color:var(--line2); }
.ubloc form{ margin:0; display:flex; }
.ubloc .nav-tab{ padding:var(--e2) var(--e3); }
.ubloc .role-chip{ background:transparent; padding:0 var(--e1) 0 0; }

/* Le logo respire, et son cercle de mise au jeu se trace au chargement. */
.logo-vif{ position:relative; display:grid; place-items:center; width:32px; height:32px; }
.logo-vif .logo{ position:relative; z-index:1; animation:logo-souffle 7s ease-in-out infinite; }
.logo-cercle{ position:absolute; inset:-6px; width:44px; height:44px; overflow:visible; pointer-events:none; }
.logo-cercle circle{ fill:none; stroke:var(--green); stroke-width:1.2; opacity:.55;
  stroke-dasharray:1; stroke-dashoffset:1;
  animation:logo-trace 1.4s cubic-bezier(.25,.8,.3,1) .2s forwards; }
.topbar a:hover .logo-cercle circle{ opacity:1; stroke:var(--green-d); }
@keyframes logo-trace{ to{ stroke-dashoffset:0; } }
@keyframes logo-souffle{ 0%,100%{ transform:scale(1); } 50%{ transform:scale(1.06); } }

/* Layout — la colonne s'élargit sur les grands écrans. À 2560 px, un plafond à
   1400 laissait 45 % de l'écran vide alors que les tableaux et les graphiques
   ont tout à gagner à respirer. Jamais plus étroite qu'avant, jamais au-delà
   de 1900, et sur un petit écran c'est la fenêtre qui commande. */
.page{ max-width:var(--colonne); margin:0 auto; padding:24px var(--marge-page) 64px; }

/* Pas de zoom CSS : il casse le dimensionnement des canvas Chart.js (boucle de
   redimensionnement dans Chrome). La lisibilité vient de la taille de police de
   base, relevée une fois pour toutes ; la page reste centrée en largeur fixe. */
/* En-tête de page : le sur-titre et son filet viennent de la couverture. Les 13
   gabarits partagent cette paire de classes, donc une seule règle les habille
   tous. */
.sectionlabel{ display:inline-flex; align-items:center; gap:var(--e3);
  font:var(--gr-app) var(--tx-2) var(--sans); color:var(--green-d); letter-spacing:.13em; text-transform:uppercase; }
.sectionlabel::before{ content:""; width:22px; height:1px; background:var(--for-line);
  flex:none; }
.pagetitle{ font:var(--gr-titre) var(--tx-8) var(--sans); letter-spacing:-.028em; margin-top:6px; }

/* Chaque page entre en montant. Avec hx-boost, .page est un élément neuf à
   chaque navigation : l'animation se rejoue donc toute seule, sans JS. */
@keyframes page-entre{ from{ opacity:0; transform:translateY(9px); } to{ opacity:1; transform:none; } }
.page{ animation:page-entre .42s cubic-bezier(.2,.7,.2,1) both; }
@media (prefers-reduced-motion:reduce){
  .page{ animation:none; }
  .logo-vif .logo, .logo-cercle circle{ animation:none; }
  .logo-cercle circle{ stroke-dashoffset:0; }
}
.card{ background:var(--panel); border:1px solid var(--line); border-radius:14px;
  padding:var(--pad-carte); }
.card.aere{ padding:var(--pad-carte-aere); }
.card.dense{ padding:var(--pad-carte-dense); }
/* `.plein` et non `.tbl-scroll` : la seconde dit que la carte DÉFILE, pas
   qu'elle est serrée. Les confondre ôtait 8 px de vertical et 14 px
   d'horizontal aux cinq cartes d'analyse du Tableau, qui défilent aussi mais
   gardent leurs marges. Ici, la table touche les bords parce qu'elle EST la
   carte : les journaux, le classement. */
.card.plein{ padding:var(--pad-carte-table); }
/* La SEULE des 53 cartes qui n'avait aucun rembourrage. Sans cette règle elle
   en gagnerait un à la seconde où `.card` en reçoit un, avant qu'un gabarit ne
   soit ouvert — et /logger/ n'apparaît dans la liste d'aucune des mesures. */
.card.nu{ padding:0; }
/* Quatre niveaux de titre, qui se distinguent enfin.
   Avant : le titre de page à 25 px, les 47 titres de carte à 15, les titres de
   volet à 16 et les sous-titres à 13,5 — un volet majeur et une sous-carte
   séparés par 2,5 px sur une page qui affiche vingt cartes à la fois. Le volet
   monte d'un cran au-dessus de la carte qu'il contient, et la sous-carte change
   de NATURE plutôt que de taille : capitales et interlettrage, ce qui crée le
   contraste sans ajouter une ligne de hauteur à la page.

   Écrit en propriétés longues, jamais avec le raccourci `font:` — celui-ci
   réinitialise `line-height` à `normal`, et c'est exactement ce qui obligeait à
   rajouter des `line-height` après coup un peu partout dans les gabarits. */
.h-title{ font-family:var(--sans); font-weight:var(--gr-fort); font-size:var(--tx-5);
  line-height:1.3; }
.h-title.h-volet{ font-size:var(--tx-6); font-weight:var(--gr-titre); }
.h-title.h-volet.pour{ color:var(--green-strong); }
.h-title.h-volet.contre{ color:var(--red-d); }
.h-title.h-sous{ font-size:var(--tx-2); font-weight:var(--gr-fort); color:var(--mut);
  text-transform:uppercase; letter-spacing:.07em; }
.h-sub{ font:var(--gr-cour) var(--tx-2) var(--sans); color:var(--mut2); }

/* KPI */
.kpi{ background:var(--panel); border:1px solid var(--line); border-radius:13px; padding:var(--e5) var(--e5); }
.kpi .v{ font:var(--gr-fort) var(--num-3) var(--mono); letter-spacing:-.8px; }
.kpi .l{ margin-top:5px; font:var(--gr-app) var(--tx-3) var(--sans); color:var(--sub); }
.kpi .e{ font:var(--gr-cour) var(--tx-2) var(--sans); color:var(--mut2); }
/* Le rythme appartient aux CONTENEURS, pas aux éléments.
   Les ~90 marges de premier niveau posées dans les gabarits ne deviennent pas
   des classes `.mb3` : elles sont supprimées. Une classe qui porte un chiffre
   rendrait de nouveau exprimable une valeur hors échelle, et un contrôle qui
   compare à un dictionnaire ferait échouer margin-bottom:14px — c'est un cran —
   tout en laissant passer margin-bottom:13px, qui n'en est pas un.
   Conséquence voulue : ajouter une carte à une page ne demande plus aucune
   classe et aucune décision. */
.page > * + *{ margin-top:var(--gouttiere); }
/* Les partiels HTMX ne sont pas enfants de .page : ils ont leur propre pile. */
#goal-panel > * + *{ margin-top:var(--pile); }
.grid{ display:grid; gap:var(--gouttiere) var(--gouttiere-x); }
.grid.serre{ gap:var(--pile); }
.pile{ display:flex; flex-direction:column; gap:var(--pile); }
.pile.serre{ gap:var(--pile-serree); }
/* Les titres de carte n'ont jamais compté sur la marge du navigateur : 47 sur
   47 la neutralisaient en ligne. Elle n'est sur aucun cran, elle part d'ici. */
.h-title, .card h2, .card h3, .card h4{ margin:0; }

/* Bars */
.bartrack{ height:var(--h-barre); background:var(--track); border-radius:4px; overflow:hidden; }
.barfill{ height:100%; border-radius:4px; }
/* Le camp, écrit une fois. Les gabarits peignaient la jauge à la main, 92 fois,
   en répétant `background:var(--green)` ou `var(--red)` : une convention tenue
   par la discipline devient une règle tenue par la feuille. La largeur, elle,
   reste en ligne — c'est une donnée calculée par le serveur, pas une décoration. */
.barfill.pour{ background:var(--green); }
.barfill.contre{ background:var(--red); }

/* Tables */
table{ width:100%; border-collapse:collapse; }
thead th{ font:var(--gr-fort) var(--tx-2) var(--sans); color:var(--mut2); text-transform:uppercase; letter-spacing:.5px;
  text-align:left; padding:0 var(--e4) var(--e4); }
tbody td{ padding:var(--e4) var(--e4); border-top:1px solid var(--hover); font:var(--gr-app) var(--tx-4) var(--sans); color:var(--ink2); vertical-align:middle; }
/* En-tête de ligne : une cellule en <th scope="row"> tombe hors de `tbody td` et
   y perdrait sa bordure et son alignement. text-align explicite, un <th> se
   centrant par défaut. */
tbody th{ padding:var(--e4) var(--e4); border-top:1px solid var(--hover); font:var(--gr-app) var(--tx-4) var(--sans);
  color:var(--ink2); vertical-align:middle; text-align:left; }
tbody tr:hover{ background:var(--panel2); }

/* Badges */
.pill{ padding:var(--e1) var(--e3); border-radius:6px; font:var(--gr-fort) var(--tx-2) var(--sans); text-transform:uppercase; letter-spacing:.4px; }
.resbadge{ padding:var(--e1) var(--e3); border-radius:6px; font:var(--gr-fort) var(--tx-2) var(--mono); }
/* Les trois camps du verdict et les trois niveaux de priorité. Ces six paires étaient
   écrites en dur dans `nm.py`, donc du CSS produit par du Python : hors de portée du
   thème, des media queries, et du cliquet qui ne lit que les gabarits. Elles reprennent
   exactement les jetons de `.pv.w/.l/.t`, qui les portaient déjà pour les verdicts de
   période : une seule vérité pour la même sémantique. */
.resbadge.res-gagne{ background:var(--for-bg); color:var(--green-d); }
.resbadge.res-perdu{ background:var(--ag-bg); color:var(--red-d); }
.resbadge.res-nul{ background:var(--neutral-bg); color:var(--mut); }
/* CRIT et HIGH partagent leur teinte : le libellé porte la distinction, et --red sur
   --ag-bg ne donnait que 4,13:1, soit le signal le plus urgent dans l'encre la moins
   lisible. */
.pill.prio-haut{ background:var(--ag-bg); color:var(--red-d); }
.pill.prio-moyen{ background:var(--gold-bg); color:var(--gold); }
.pill.prio-bas{ background:var(--neutral-bg); color:var(--mut); }

/* Buttons */
.btn-primary{ padding:var(--e4) var(--e6); border:none; border-radius:9px; background:var(--green-d); color:var(--on-green);
  font:var(--gr-fort) var(--tx-4) var(--sans); cursor:pointer; box-shadow:0 1px 2px var(--green-glow); display:inline-flex; align-items:center; gap:var(--e2); }
.btn-primary:hover{ background:var(--green-h); }
.btn-ghost{ padding:var(--e4) var(--e5); border:1px solid var(--line2); border-radius:9px; background:var(--panel);
  font:var(--gr-fort) var(--tx-4) var(--sans); color:var(--mut); cursor:pointer; }
.btn-ghost:hover{ background:var(--panel2); }
/* Le rouge des actions irréversibles. Il ne partage pas la teinte des buts
   contre : `--red` dit « l'adversaire a marqué », un fait du match, alors qu'ici
   il avertit. La distinction tient parce que les deux ne se croisent jamais sur
   la même page. */
.btn-danger{ padding:var(--e4) var(--e6); border:none; border-radius:9px;
  background:var(--red-d); color:var(--panel);
  font:var(--gr-fort) var(--tx-4) var(--sans); cursor:pointer;
  display:inline-flex; align-items:center; gap:var(--e2); }
.btn-danger:hover{ background:var(--red); }
.btn-danger:disabled{ opacity:.55; cursor:not-allowed; }
.btn-danger:disabled:hover{ background:var(--red-d); }
/* htmx désactive « Enregistrer le but » et « Analyser le fichier » pendant la
   requête (hx-disabled-elt) : sans ces règles, rien ne distinguait un bouton
   inerte d'un bouton actif et on recliquait dans le vide. */
.btn-primary:disabled, .btn-ghost:disabled{ opacity:.55; cursor:not-allowed; box-shadow:none; }
.btn-primary:disabled:hover{ background:var(--green-d); }
.btn-ghost:disabled:hover{ background:var(--panel); }

/* Forms */
.field{ width:100%; padding:var(--e3) var(--e4); background:var(--panel); border:1px solid var(--line2); border-radius:9px;
  font:var(--gr-app) var(--tx-4) var(--sans); color:var(--ink); appearance:none; }
.field:focus{ outline:none; border-color:var(--green); box-shadow:0 0 0 3px var(--green-glow2); }
.field-lbl{ font:var(--gr-app) var(--tx-2) var(--sans); color:var(--mut); margin-bottom:5px; display:block; }
.selwrap{ position:relative; }
.selwrap::after{ content:"▾"; position:absolute; right:11px; top:50%; transform:translateY(-50%);
  pointer-events:none; color:var(--fleche); font-size:var(--tx-3); }
/* Place réservée au chevron : les 12 px de padding de .field laissaient l'option
   la plus longue s'imprimer par-dessus le ▾, qui devenait illisible. */
.selwrap > .field{ padding-right:var(--e8); }

/* Champ fichier. Le bouton est la seule partie stylable d'un `input[type=file]`,
   par `::file-selector-button` ; le libellé « Aucun fichier sélectionné » qui le
   suit appartient au navigateur et ne se remplace pas sans JavaScript. Le champ
   reste donc natif, avec son sélecteur système et son accessibilité intacts, et
   c'est le bouton qui rejoint le système. */
.fichier{ font:var(--gr-cour) var(--tx-3) var(--sans); color:var(--mut); max-width:100%; }
.fichier::file-selector-button{ margin-right:var(--e3); padding:var(--e2) var(--e4);
  border:1px solid var(--line2); border-radius:8px; background:var(--panel2);
  color:var(--ink2); font:var(--gr-fort) var(--tx-3) var(--sans); cursor:pointer;
  transition:background var(--t), border-color var(--t); }
.fichier::file-selector-button:hover{ background:var(--hover); border-color:var(--line3); }

/* Deux libellés cliquables portaient leur apparence ENTIÈRE en style en ligne, bordure,
   rembourrage et fond compris : la zone de dépôt de l'import et la case du formulaire
   Match. Le repli mobile devait donc les viser par `label.fl.centre`, un sélecteur qui
   décrit leur mise en page et non leur rôle, et qui attrape aussi trois libellés du
   panneau de saisie qui n'ont rien à voir.

   Les rembourrages en ligne valaient 10px 12px et les écarts 9 et 10 px : aucun n'est un
   cran de l'échelle. Ils tombent sur `--e4` (11 px) et `--e3` (8 px), soit un pixel de
   report sur chaque axe, ce qui rend ces règles muettes pour le compteur de littéraux. */
.zone-depot{ display:flex; align-items:center; gap:var(--e4); padding:var(--e4);
  border:1px dashed var(--line2); border-radius:9px; background:var(--panel); cursor:pointer; }
.case-bandeau{ grid-column:1 / -1; display:flex; align-items:center; gap:var(--e3);
  padding:var(--e4); border:1px solid var(--line2); border-radius:10px;
  background:var(--panel); cursor:pointer; }

/* Segments et pastilles */
.seg{ flex:1; padding:var(--e3) var(--e4); border:1px solid var(--line2); border-radius:9px; background:var(--panel);
  color:var(--mut); font:var(--gr-fort) var(--tx-3) var(--sans); cursor:pointer; text-align:center; }
.seg.on{ border-color:var(--green-d); background:var(--green-d); color:var(--on-green); }
/* Variante compacte : les barres de tri de la Watchlist et de la Synthèse alignent
   onze boutons sur une rangée. flex:none est indispensable, sinon ils héritent du
   flex:1 de .seg et s'étirent chacun sur toute la largeur. */
.seg.seg-mini{ flex:none; padding:var(--e1) var(--e4); font-size:var(--tx-2); }
.chip{ position:relative; display:inline-flex; align-items:center; gap:var(--e2); padding:var(--e2) var(--e4) var(--e2) var(--e3); background:var(--panel);
  border:1px solid var(--line2); border-radius:20px; font:var(--gr-app) var(--tx-3) var(--sans); color:var(--ink2); cursor:pointer; user-select:none; }
.chip.on{ background:var(--accent-soft); border-color:var(--for-line); }
/* Filtre appliqué des journaux GF/GA. La graisse et l'anneau doublent le fond vert
   d'un indice non chromatique : en vision monochrome, les pastilles ne se
   distinguaient que par la teinte. */
.chip[aria-current]{ background:var(--green-dd); border-color:var(--green-dd);
  color:var(--on-green-dd); font-weight:800; box-shadow:0 0 0 1px var(--green-dd); }
.chip .n{ font:var(--gr-fort) var(--tx-2) var(--mono); color:var(--green-d); }
/* Checkbox masquée mais focusable (tabulation + lecteurs d'écran) */
.chip > input[type=checkbox]{ position:absolute; opacity:0; width:1px; height:1px; pointer-events:none; }
.chip:focus-within{ outline:2px solid var(--green-d); outline-offset:2px; }
.chip.fault-chip.on{ background:var(--ag-bg); border-color:var(--ag-line); }
/* :not(.on) obligatoire : sans lui, survoler une pastille cochée lui retirerait
   son fond vert et donnerait l'impression de l'avoir décochée. Même raison pour
   [aria-current], que portent les pastilles de filtre des journaux. */
.seg:hover:not(.on){ background:var(--panel2); border-color:var(--line3); }
.seg:active{ transform:translateY(1px); }
.chip:hover:not(.on):not([aria-current]){ background:var(--hover); border-color:var(--line3); }
.chip:active{ transform:translateY(1px); }

/* ── Deux motifs de formulaire qui vivaient en style en ligne ────────────────
   Ils étaient écrits à la main dans `game_form` et dans `donnees`, avec bordure,
   rembourrage et fond recopiés. Le formulaire de l'effectif en avait besoin des
   deux, et un gabarit neuf n'a aucune raison de commencer une dette : ils
   deviennent des composants, ce qui donne aussi le point d'accroche pour retirer
   les copies en ligne des deux anciens.

   Composants et non utilitaires : la couche d'utilitaires est FERMÉE à 14
   sélecteurs, et une case à cocher encadrée n'est pas une aide de mise en page,
   c'est une pièce du produit. */
.case-bloc{ display:flex; align-items:center; gap:var(--e4); padding:var(--e4) var(--e5);
  background:var(--panel); border:1px solid var(--line2); border-radius:10px;
  cursor:pointer; }
.case-bloc:hover{ background:var(--hover); border-color:var(--line3); }
.case-bloc input[type=checkbox]{ flex:0 0 auto; accent-color:var(--green-d); }
/* Le filet du haut sépare la décision de la saisie : sans lui, le bouton
   d'enregistrement se lisait comme un champ de plus. */
.barre-actions{ display:flex; align-items:center; justify-content:flex-end;
  gap:var(--e4); padding-top:var(--e5); border-top:1px solid var(--line); }

/* Focus clavier visible partout (les .field ont déjà le leur) */
a:focus-visible, button:focus-visible, [tabindex]:focus-visible, .seg:focus-visible{
  outline:2px solid var(--green-d); outline-offset:2px; border-radius:6px; }
.toast{ position:fixed; bottom:22px; left:50%; transform:translateX(-50%); z-index:60; display:flex;
  align-items:center; gap:var(--e4); padding:var(--e4) var(--e6); background:var(--green-dd); color:var(--on-green-dd); border-radius:11px;
  box-shadow:0 12px 30px rgba(var(--ombre),.3); font:var(--gr-fort) var(--tx-5) var(--sans); animation:pop .2s ease; }
/* Le conteneur des messages. `pointer-events:none` sur lui, `auto` sur chaque
   pilule : il fait 560 px de large pour un message bien plus étroit, en position
   fixe au-dessus du bas de page, et ses zones transparentes avalaient les clics
   pendant les 3,2 à 8 secondes d'affichage. */
.toast-root{ position:fixed; bottom:22px; left:50%; transform:translateX(-50%); z-index:60;
  flex-direction:column; gap:var(--e3); width:min(92vw, 560px); pointer-events:none; }
/* Dans le conteneur, la pilule cède sa position fixe : c'est lui qui la porte. */
.toast-root > .toast{ position:static; transform:none; pointer-events:auto; }
/* Le padding était à 2px 3px en style en ligne, hors échelle : en entrant dans la
   feuille il passe au premier cran, ce qui agrandit la cible de deux pixels. */
.toast-x{ border:none; background:none; color:inherit; line-height:1; cursor:pointer;
  font:var(--gr-fort) var(--tx-5) var(--mono); padding:var(--e1); outline-color:currentColor; }

/* ---------------------------------------------------------------------------
   Composants « parité gabarit » — transposition de la grammaire visuelle du
   classeur Excel (camps Pour/Contre, tables denses, vocabulaires complets)
   dans le design system existant. La sémantique de camp reste --green/--red.
--------------------------------------------------------------------------- */

/* Cellules de valeurs teintées par camp (équivalent des fonds pâles du classeur) */
.val-for{ background:var(--for-bg); color:var(--green-d); padding:2px var(--e3); border-radius:6px;
  font:var(--gr-fort) var(--tx-3) var(--mono); font-variant-numeric:tabular-nums; }
.val-ag{ background:var(--ag-bg); color:var(--red-d); padding:2px var(--e3); border-radius:6px;
  font:var(--gr-fort) var(--tx-3) var(--mono); font-variant-numeric:tabular-nums; }
th.th-for{ color:var(--green-d); }
th.th-ag{ color:var(--red-d); }

/* Tuile KPI héro (totaux GF/GA jumeaux) — variante grand format du .kpi */
.kpi-hero{ border:1px solid var(--line); border-radius:13px; padding:var(--e5) var(--e6); background:var(--panel); }
.kpi-hero .v{ font:var(--gr-fort) var(--num-4) var(--mono); letter-spacing:-1.2px; line-height:1.05; }
.kpi-hero .s{ font:var(--gr-app) var(--tx-3) var(--mono); color:var(--mut); margin-top:2px; }
.kpi-hero .l{ margin-top:6px; font:var(--gr-app) var(--tx-3) var(--sans); color:var(--sub); }
.kpi-hero .e{ font:var(--gr-cour) var(--tx-2) var(--sans); color:var(--mut2); }
.kpi-hero.for{ background:var(--for-bg2); } .kpi-hero.for .v{ color:var(--green-d); }
.kpi-hero.ag{ background:var(--ag-bg2); } .kpi-hero.ag .v{ color:var(--red-d); }

/* Accent de camp en tête de card (volets miroirs, journaux GF/GA) */
.card.acc-for{ border-top:3px solid var(--green); }
.card.acc-ag{ border-top:3px solid var(--red); }

/* Tables denses & en-têtes à 2 niveaux */
.table-dense tbody td, .table-dense tbody th{ padding:var(--e2) var(--e3); font:var(--gr-app) var(--tx-3) var(--sans); }
.table-dense thead th{ padding:0 var(--e3) var(--e3); }
.num{ font-family:var(--mono); font-variant-numeric:tabular-nums; }
th.grp{ font:var(--gr-fort) var(--tx-2) var(--sans); color:var(--mut2); text-transform:uppercase;
  letter-spacing:.6px; text-align:center; padding:0 var(--e3) var(--e1); }
.grp-b{ border-left:1px solid var(--line2); }
/* Les six en-têtes de groupe teintés de la Watchlist portaient leur fond en style en
   ligne, hors de portée du cliquet et du contrôle de contraste.

   DEUX PIÈGES, tous deux évités ici. Le premier est la SPÉCIFICITÉ : `th.grp` ci-dessus
   vaut (0,1,1), donc une classe nue `.grp-pour{background:…;color:…}` à (0,1,0) verrait
   son fond s'appliquer et son `color` se faire battre, sans erreur ni signe. Le type
   `th` est donc obligatoire dans chaque sélecteur.

   Le second est l'ENCRE. Ces règles nomment `--sub` et non le `--mut2` hérité : sur
   `--gold-bg`, --mut2 mesure 4,5888 contre 4,5 exigé, soit 0,09 de marge, ce que le
   contrôle de contraste refuse justement (il rejette toute paire vivant à moins de 0,1
   de son seuil). --sub tient 8,04 en jour et 9,36 en nuit sur ce même fond. Les cinq
   surfaces sont désormais couvertes par des paires nommées, y compris les deux qui ne
   l'étaient par rien : bleu et or. */
th.grp-pour{ background:var(--for-bg2); color:var(--sub); }
th.grp-sp{ background:var(--blue-bg); color:var(--sub); }
th.grp-so{ background:var(--gold-bg); color:var(--sub); }
th.grp-ag{ background:var(--ag-bg2); color:var(--sub); }
th.grp-pts{ background:var(--accent-soft); color:var(--sub); }
.tbl-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch;
  /* Ombres de défilement : signalent le contenu hors-champ. La lueur passait par
     --ombre, qui vaut du noir la nuit : sur un panneau sombre elle disparaissait
     complètement. --scroll-cue s'éclaircit avec le thème. */
  background:
    linear-gradient(90deg, var(--panel) 30%, transparent) left/40px 100% no-repeat local,
    linear-gradient(270deg, var(--panel) 30%, transparent) right/40px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--scroll-cue), transparent) left/14px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--scroll-cue), transparent) right/14px 100% no-repeat scroll,
    var(--panel); }
/* Pas d'en-tête collant ici : `overflow-x:auto` fait calculer overflow-y en auto,
   donc un thead en position:sticky se réfère à .tbl-scroll et non à la page. Sans
   hauteur plafonnée ce conteneur ne défile jamais verticalement et le collage ne
   se déclenche pas : le rétablir suppose un défilement interne, à décider table
   par table. */
tfoot td{ padding:var(--e3) var(--e3); border-top:2px solid var(--line2); background:var(--panel2);
  font:var(--gr-fort) var(--tx-3) var(--mono); font-variant-numeric:tabular-nums; color:var(--ink2); }
/* La pastille de notes des matchs est un <summary> : `list-style:none` suffit à
   masquer le triangle sur Chrome, Firefox et Safari 16.4+, mais les Safari plus
   anciens — l'iPad du banc — génèrent encore ce pseudo-élément et affichaient un
   second triangle à côté du point doré. */
summary::-webkit-details-marker{ display:none; }

/* Grilles utilitaires — colonnes desktop identiques aux anciennes valeurs inline,
   mais qui savent se replier (voir le bloc Responsive en fin de fichier). */
.grid.cols-2{ grid-template-columns:1fr 1fr; }
.grid.cols-2a{ grid-template-columns:1.15fr 1fr; }
.grid.cols-3{ grid-template-columns:repeat(3,1fr); }
.grid.cols-4{ grid-template-columns:repeat(4,1fr); }
.grid.cols-ctx{ grid-template-columns:1.2fr 1fr 1fr; }
.grid.cols-pod{ grid-template-columns:repeat(5,1fr); }
.grid.cols-kpi{ grid-template-columns:repeat(6,1fr); }
.grid.cols-rec{ grid-template-columns:1.3fr repeat(7,1fr); }

/* Grille de formulaire (panneau de saisie, fiche match) */
.form-grid{ display:grid; grid-template-columns:1fr 1fr; gap:var(--e4); }

/* Micro-badges sémantiques */
.pv{ display:inline-block; min-width:18px; text-align:center; padding:2px var(--e1);
  border-radius:5px; font:var(--gr-fort) var(--tx-2) var(--mono); }
.pv.w{ background:var(--for-bg); color:var(--green-d); }
.pv.l{ background:var(--ag-bg); color:var(--red-d); }
.pv.t{ background:var(--neutral-bg); color:var(--mut); }
.gstate{ padding:2px var(--e2); border-radius:6px; font:var(--gr-fort) var(--tx-2) var(--sans);
  text-transform:uppercase; letter-spacing:.4px; white-space:nowrap; }
.gstate.lead{ background:var(--for-bg); color:var(--green-d); }
.gstate.tied{ background:var(--neutral-bg); color:var(--mut); }
.gstate.trail{ background:var(--ag-bg); color:var(--red-d); }
.flag{ display:inline-block; padding:2px var(--e2); border-radius:5px; font:var(--gr-fort) var(--tx-1) var(--mono);
  background:var(--accent-soft); color:var(--green-d); }
.pchip{ display:inline-block; padding:2px var(--e2); border-radius:6px; font:var(--gr-fort) var(--tx-3) var(--mono);
  background:var(--tag-bg); color:var(--ink2); }
.pchip.f{ color:var(--green-d); } .pchip.d{ color:var(--blue); } .pchip.g{ color:var(--gold); }
.pchip.fault{ background:var(--panel); border:1px solid var(--red); color:var(--red-d); }

/* Chevron déplier/replier des journaux GF/GA — jugé trop discret sur tablette.
   C'est un <button> depuis qu'il a cessé d'être une ligne au rôle « button » :
   d'où l'annulation des styles système, sinon il reprend bordure, fond gris et
   police du navigateur au milieu de la cellule. */
.rowtoggle{ display:inline-flex; align-items:center; justify-content:center;
  min-width:26px; min-height:26px; font-size:var(--tx-6); color:var(--mut);
  border:0; background:none; padding:0; cursor:pointer; font-family:inherit; }

/* Podiums Top / À surveiller (watchlist) */
.podium{ display:flex; flex-direction:column; gap:var(--e1); }
.podium .row{ display:flex; justify-content:space-between; align-items:center;
  padding:var(--e1) var(--e3); border-radius:7px; font:var(--gr-app) var(--tx-3) var(--sans); }
.podium .row .v{ font:var(--gr-fort) var(--tx-3) var(--mono); }
.podium.top .row{ background:var(--for-bg); color:var(--green-strong); }
.podium.top .row .v{ color:var(--green-d); }
.podium.worst .row{ background:var(--ag-bg); color:var(--red-d); }

/* Distributions vocabulaire complet (zéros grisés ; .vlist = version 2 colonnes) */
.vlist{ column-count:2; column-gap:22px; }
.vrow{ break-inside:avoid; display:flex; align-items:center; gap:var(--e3);
  padding:var(--e1) 0; }
/* Les quatre colonnes de la ligne « libellé · jauge · n · % ». Écrites ici, les
   trois pistes d'une bande de trois cartes commencent enfin au même x — l'œil
   lisait un défaut d'alignement sans pouvoir le nommer. */
.vlbl{ flex:0 0 var(--lbl); font:var(--gr-app) var(--tx-3) var(--sans); color:var(--ink2);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.vrow .bartrack{ flex:1; }
.vn{ flex:0 0 var(--col-n); text-align:right;
  font:var(--gr-fort) var(--tx-4) var(--mono); font-variant-numeric:tabular-nums; }
.vp{ flex:0 0 var(--col-p); text-align:right;
  font:var(--gr-app) var(--tx-2) var(--mono); color:var(--mut2); }
/* --mut2 et non --mut3 : ces lignes à zéro portent de l'information (« jamais
   en cycle »), et --mut3 ne tient que 3,1:1 sur le blanc du mode jour. */
.vrow.zero .vlbl, .vrow.zero .vn{ color:var(--mut2); }

/* Ancres (deep-links vers un match ou un but) : dégager la topbar sticky */
[id]{ scroll-margin-top: 74px; }
tbody[id]:target tr:first-child, tr[id]:target{ animation:flash 2.4s ease; }

/* Drill-down : toute ligne/carte agrégée cliquable vers son journal filtré */
a.vrow.drill{ margin:0 -6px; padding:var(--e1) var(--e2); border-radius:6px; }
a.vrow.drill:hover{ background:var(--hover); }
a.vrow.drill:hover .vlbl{ color:var(--green-strong); }
a.subcard.drill:hover{ border-color:var(--line2); background:var(--panel3); }
tbody td a:hover{ text-decoration:underline; text-underline-offset:3px; }

/* Barre duale (deux camps face à face dans la même piste) */
.dualbar{ display:flex; height:7px; border-radius:4px; overflow:hidden; background:var(--track); }
.dualbar .a{ background:var(--green); } .dualbar .b{ background:var(--ag-line2); }
/* Les deux segments que les gabarits repeignaient à la main. `.b` porte un
   rouge sourd, celui d'une fiche de match où la défaite est un fait ; `.vif`
   est le rouge d'un compteur de buts contre, où elle est le sujet. Sans ces
   deux classes, la sémantique de camp restait une convention tenue à la main
   sur ce composant, alors que `.barfill` l'avait déjà quittée. */
.dualbar .b.vif{ background:var(--red); }
.dualbar .n{ background:var(--line3); }

/* Détail dépliable des journaux + sous-cartes coaching */
.subcard{ background:var(--panel2); border:1px solid var(--line); border-radius:11px; padding:var(--e4) var(--e5); }
.detail-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:var(--e4); }
.detail-grid .dl{ font:var(--gr-app) var(--tx-2) var(--sans); color:var(--mut2); text-transform:uppercase; letter-spacing:.5px; }
.detail-grid .dv{ font:var(--gr-app) var(--tx-4) var(--sans); color:var(--ink2); margin-top:2px; }
.foot-quote{ border-top:1px solid var(--line); margin-top:12px; padding-top:var(--e3);
  font:var(--gr-cour) var(--tx-2) var(--sans); color:var(--mut2); }

/* En-tête de groupe de lignes (journaux regroupés par match) */
tr.grouphead td{ background:var(--panel2); border-top:1px solid var(--line2);
  font:var(--gr-fort) var(--tx-3) var(--sans); color:var(--ink2); padding:var(--e3) var(--e4); }

/* Barre de progression des navigations HTMX (hx-boost) */
#nprog{ position:fixed; top:0; left:0; height:2px; width:0; background:var(--green); z-index:99; opacity:0; }
#nprog.run{ opacity:1; width:70%; transition:width 8s cubic-bezier(.1,.7,.1,1); }
#nprog.done{ opacity:0; width:100%; transition:width .2s ease, opacity .4s ease .1s; }

/* Indicateurs de requête HTMX (boutons de formulaire) */
.htmx-indicator{ opacity:0; transition:opacity .15s ease; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator{ opacity:1; }

/* ── Couche superposée : palette de commandes et dialogue de suppression ─────
   Deux occupants, un seul voile. Les invariants sont partagés pour qu'ils ne
   puissent pas dériver : un second voile écrit à la main aurait son propre
   z-index, et deux couches concurrentes finissent toujours par se croiser. */
.pal-overlay, .modale-voile{ position:fixed; inset:0; z-index:70;
  background:rgba(var(--ombre),.35); display:flex; align-items:flex-start;
  justify-content:center; animation:fadeIn .12s ease; }
/* Propre à la palette : elle se pose haut, comme un champ de recherche. */
.pal-overlay{ padding-top:12vh; }
/* Le dialogue de suppression, lui, est un formulaire : il respire, et c'est le VOILE
   qui défile, pas la carte. `.field:focus` porte une ombre EXTÉRIEURE de 3 px qu'un
   `overflow` intérieur rognerait. `overscroll-behavior` empêche le défilement de se
   chaîner à la page derrière, ce que l'inertie ne fait pas. */
.modale-voile{ padding:var(--e8) var(--e5); overflow-y:auto; overscroll-behavior:contain; }
.modale-voile > .card{ width:100%; box-shadow:0 30px 70px rgba(var(--ombre),.35); }
.pal{ width:560px; max-width:92vw; background:var(--panel); border-radius:14px;
  box-shadow:0 30px 70px rgba(var(--ombre),.35); overflow:hidden; }
.pal-input{ width:100%; border:none; outline:none; padding:var(--e5) var(--e6); font:var(--gr-app) var(--tx-5) var(--sans);
  color:var(--ink); border-bottom:1px solid var(--line); }
/* L'anneau natif est retiré : il faut donc marquer le retour du focus dans le
   champ quand on remonte de la liste au clavier. Ombre intérieure, car .pal
   est en overflow:hidden et rognerait une ombre portée. */
.pal-input:focus{ border-bottom-color:var(--green); box-shadow:inset 0 -2px 0 var(--green-d); }
.pal-list{ max-height:330px; overflow-y:auto; padding:var(--e2); }
.pal-item{ display:flex; justify-content:space-between; align-items:center; gap:var(--e4);
  padding:var(--e4) var(--e4); border-radius:8px; font:var(--gr-app) var(--tx-4) var(--sans); color:var(--ink2); }
.pal-item.on{ background:var(--accent-soft); color:var(--green-strong); }
.pal-kind{ font:var(--gr-app) var(--tx-2) var(--sans); color:var(--mut2); white-space:nowrap; }
.pal-empty{ padding:var(--e6); text-align:center; font:var(--gr-cour) var(--tx-3) var(--sans); color:var(--mut2); }
.pal-foot{ padding:var(--e3) var(--e5); border-top:1px solid var(--line); font:var(--gr-cour) var(--tx-2) var(--sans);
  color:var(--mut2); background:var(--panel2); }

/* Lien d'évitement (navigation clavier / lecteurs d'écran) */
.skip{ position:absolute; left:-9999px; }
.skip:focus{ position:fixed; top:10px; left:10px; z-index:100; background:var(--green-dd);
  color:var(--on-green-dd); padding:var(--e4) var(--e5); border-radius:9px; font:var(--gr-fort) var(--tx-4) var(--sans); }

/* Bouton discret qui ressemble à du texte (copie de clip, etc.) */
.linklike{ border:none; background:none; padding:0; cursor:copy; font:inherit; color:inherit; }

/* Texte réservé aux lecteurs d'écran : les <caption> qui nomment les tableaux, les
   libellés qu'on ne veut pas voir deux fois. Pas de display:none ni de
   visibility:hidden, qui le retireraient aussi de la vocalisation. */
.sr-only{ position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }

/* ── Les rôles de texte ─────────────────────────────────────────────────────
   226 blocs en ligne couplent une couleur ET une police : ce ne sont pas deux
   décorations posées côte à côte, c'est un rôle. Écrits à la main, ils
   produisaient 101 formes distinctes pour une dizaine d'intentions réelles.

   Propriétés longues, jamais le raccourci `font:` — il réinitialise
   line-height à `normal`, ce qui est la cause des line-height rajoutés après
   coup dans les gabarits.

   Spécificité (0,1,0) et jamais plus. Un sélecteur double placé en fin de
   fichier surclasserait tout le bloc @media (pointer:coarse), y compris
   `.seg.seg-mini` : la consigne serait respectée à la lettre et violée en
   pratique. Là où une règle existante gagne — `.kpi .l`, `.eff-chip .pm`,
   `.table-dense tbody td` — on passe par un composant ou on laisse le style en
   ligne. Jamais par un doublement. */
.t-lbl{ font-family:var(--sans); font-weight:var(--gr-app); font-size:var(--tx-3);
  line-height:1.3; color:var(--mut2); }
.t-meta{ font-family:var(--sans); font-weight:var(--gr-cour); font-size:var(--tx-3);
  line-height:1.45; color:var(--mut2); }
.t-hint{ font-family:var(--sans); font-weight:var(--gr-cour); font-size:var(--tx-1);
  line-height:1.4; color:var(--mut2); }
.t-prose{ font-family:var(--sans); font-weight:var(--gr-cour); font-size:var(--tx-4);
  line-height:1.55; color:var(--sub); max-width:var(--mesure-prose); }
.t-cap{ font-family:var(--sans); font-weight:var(--gr-app); font-size:var(--tx-1);
  text-transform:uppercase; letter-spacing:.06em; color:var(--mut2); }
.t-code{ font-family:var(--mono); font-weight:var(--gr-fort); font-size:var(--tx-4);
  color:var(--green-d); white-space:nowrap; }
.t-lien{ font-family:var(--sans); font-weight:var(--gr-app); font-size:var(--tx-3);
  color:var(--blue); }

/* Les valeurs sont nommées par le CAMP, jamais par la teinte : `.v-pour` et non
   `.v-vert`. La sémantique intouchable — vert = buts pour, rouge = buts contre —
   cesse d'être une convention répétée à la main dans chaque gabarit et devient
   une règle écrite une fois, à un seul endroit comptable. */
.v-pour{ color:var(--green-d); }
.v-contre{ color:var(--red-d); }
.v-neutre{ color:var(--mut); }
.v-pour-vif{ color:var(--green); }
.v-contre-vif{ color:var(--red); }
.t-erreur{ font-family:var(--sans); font-weight:var(--gr-app); font-size:var(--tx-3);
  color:var(--red-d); margin-top:var(--e1); }

/* ── Les en-têtes, écrits une fois ──────────────────────────────────────────
   Le titre à gauche, le compte ou l'action à droite : neuf blocs le
   réécrivaient à la main en flex, avec quatre alignements différents. */
.entete-page{ display:flex; align-items:flex-end; justify-content:space-between;
  flex-wrap:wrap; gap:var(--e2) var(--e5); }
.entete-carte{ display:flex; align-items:center; justify-content:space-between;
  gap:var(--e3); flex-wrap:wrap; margin-bottom:var(--pile); }
.entete-carte.base{ align-items:baseline; }
.entete-carte > :first-child{ min-width:0; }
.barre-filtres{ display:flex; align-items:center; flex-wrap:wrap; gap:var(--e2); }
/* Une rangée de boutons : l'action d'abord, l'échappatoire ensuite. */
.barre-boutons{ gap:var(--e3); }
/* Un formulaire se lit d'autant mieux qu'il ne s'étire pas : à 1728 px, une
   carte pleine largeur met le champ de saisie à un mètre de son libellé.

   Cette mesure était écrite TROIS FOIS avec trois valeurs : 640 ici, 620 en dur
   dans `goal_log.html` et 680 dans `game_form.html`. Les deux littéraux cèdent
   leur place, et les trois formulaires partagent enfin la même mesure. */
.mesure-formulaire{ max-width:var(--mesure-formulaire); margin-inline:auto; }

/* ── La saisie sur écran large : deux colonnes de BLOCS ──────────────────────
   Mesuré à 1990 px de fenêtre : le panneau occupait 620 px sur 1791 disponibles,
   donc 65 % de la largeur perdue, pendant que le formulaire faisait 1410 px de
   haut — 1,6 écran à faire défiler pendant qu'on saisit, au banc, entre deux
   périodes.

   Ce qui s'élargit est la DISPOSITION, jamais un champ, et la largeur du palier
   se DÉRIVE au lieu de s'écrire. La première version portait 1328 px, une
   addition fausse — deux fois la mesure plus une gouttière que je croyais de
   48 px — qui oubliait le chrome rendu et élargissait le champ de 17,5 px pour
   ne rien payer. Le compte juste : 46 px de chrome (2 px de bordures de `.card`
   et 44 px de rembourrage en ligne du `<form>`), donc chaque colonne demande
   `--mesure-formulaire - 46`, et l'enveloppe en demande deux plus la gouttière
   plus son propre chrome. Cela donne 1258 px, et le champ vaut 291,5 px des DEUX
   côtés du palier. Vérifié au navigateur, pas calculé de tête.

   Ce que l'unification a coûté, et qu'il faut dire : le panneau de saisie était
   plafonné à 620 px, il passe à 640, donc son champ gagne 10 px. C'est le prix
   d'avoir UNE mesure au lieu de trois, et 640 est justement celle que le
   commentaire ci-dessus défend avec sa mesure.

   Le palier est à 1400 px, au-dessus de la plus haute `max-width` de la feuille
   (1100) : il ne peut ni recouvrir ni réordonner le domaine mobile. */
.mesure-saisie{ max-width:var(--mesure-formulaire); margin-inline:auto; }
.saisie-colonnes > .saisie-bloc{ min-width:0; }

/* ── Le formulaire de match : une grille homogène, donc une TROISIÈME piste ──
   Le panneau de saisie se coupe en blocs parce qu'il en a six ; celui du match
   est UNE seule grille de 23 champs sur onze rangées, sans rien à répartir. Le
   geste utile y est donc l'inverse : ajouter une piste, ce qui retire trois
   rangées.

   Mesuré à 1990 px : 640 px de formulaire sur 1791 disponibles, 64 % de largeur
   perdue, 909 px de haut. J'avais écrit qu'il ne portait « que cinq ou six
   champs » et que le gain n'y serait pas mesurable : c'était faux, il en porte
   PLUS que la saisie, et le client l'a relevé. */
.mesure-match{ max-width:var(--mesure-formulaire); margin-inline:auto; }

@media (min-width:1400px){
  .mesure-saisie{ max-width:calc(2 * (var(--mesure-formulaire) - 46px)
                                 + var(--e7) + 46px); }
  /* Trois pistes de la MÊME largeur qu'à deux : on part de la largeur d'une piste
     à la mesure de base, `(mesure - chrome - gouttière) / 2`, et on en demande
     trois plus deux gouttières plus le chrome. Le champ ne bouge donc pas, et la
     valeur ne peut pas dériver si la mesure de base change. */
  .mesure-match{ max-width:calc(3 * ((var(--mesure-formulaire) - 46px - var(--e4)) / 2)
                                + 2 * var(--e4) + 46px); }
  .grille-large{ grid-template-columns:repeat(3, 1fr); }
  /* `align-items:start` : sans lui les deux colonnes s'étireraient à la hauteur de
     la plus haute, et le fond de la plus courte descendrait dans le vide. */
  .saisie-colonnes{ display:grid; grid-template-columns:1fr 1fr; gap:0 var(--e7);
    align-items:start; }
}

/* ── Le menu du compte ──────────────────────────────────────────────────────
   Une seule cible dans la barre au lieu de six. Langue, ambiance et
   déconnexion sont des actions rares : leur place est ici, pas au premier
   niveau où elles poussaient les onglets hors de vue sur un écran étroit. */
.menu-compte{ position:relative; }
/* min-height 44 px, sans media query : c'est la cible tactile minimale, et la
   régler ici plutôt que dans le bloc `pointer:coarse` évite d'y toucher — il
   est gelé. Un menu confortable au doigt ne gêne personne à la souris. */
.menu-declencheur{ display:inline-flex; align-items:center; gap:var(--e2);
  min-height:44px; padding:var(--e1) var(--e3) var(--e1) var(--e1); border:1px solid var(--line2);
  border-radius:999px; background:var(--panel); color:var(--ink2); cursor:pointer;
  font:var(--gr-app) var(--tx-3)/1 var(--sans);
  transition:background var(--t), border-color var(--t); }
.menu-declencheur:hover{ background:var(--hover); border-color:var(--line3); }
.menu-declencheur[aria-expanded="true"]{ background:var(--hover); border-color:var(--green); }
.menu-chevron{ color:var(--mut2); transition:transform var(--t); }
.menu-chevron.ouvert{ transform:rotate(180deg); }

/* Aucune media query ici, et c'est délibéré : le bloc responsive du produit est
   gelé par consigne, et une règle de plus y aurait rendu son empreinte fausse.
   Le panneau tient donc à toutes les largeurs par construction — il ne dépasse
   jamais la fenêtre, et ses entrées sont taillées pour le doigt partout. */
.menu-panneau{ position:absolute; right:0; top:calc(100% + 7px); z-index:40;
  min-width:248px; max-width:calc(100vw - var(--e7)); padding:var(--e2);
  border:1px solid var(--line2); border-radius:13px; background:var(--panel);
  box-shadow:0 18px 40px -18px rgba(var(--ombre),.34), 0 2px 6px rgba(var(--ombre),.06); }
.menu-tete{ padding:var(--e3) var(--e4) var(--e4); border-bottom:1px solid var(--line);
  margin-bottom:var(--e2); display:flex; align-items:baseline;
  justify-content:space-between; gap:var(--e3); flex-wrap:wrap; }
.menu-tete-nom{ font:var(--gr-fort) var(--tx-4)/1.3 var(--sans); color:var(--ink); }
.menu-panneau form{ margin:0; }
.menu-item{ display:flex; align-items:center; gap:var(--e3); width:100%;
  min-height:44px; padding:var(--e3) var(--e4); border:0; border-radius:9px;
  background:none; color:var(--ink2); text-align:left; cursor:pointer;
  font:var(--gr-app) var(--tx-4)/1.35 var(--sans);
  transition:background var(--t), color var(--t); }
.menu-item svg{ color:var(--mut2); flex:none; }
.menu-item:hover{ background:var(--hover); }
.menu-item:hover svg{ color:var(--green-d); }
/* La page courante, quand elle vit dans le menu : sans marque, on rouvre l'aide
   en croyant en sortir. */
.menu-item[aria-current]{ background:var(--for-bg2); color:var(--green-strong); }
.menu-item[aria-current] svg{ color:var(--green-d); }
.menu-touche{ margin-left:auto; padding:2px var(--e2); border:1px solid var(--line2);
  border-radius:6px; background:var(--panel2); color:var(--mut2);
  font:var(--gr-fort) var(--tx-1)/1.5 var(--mono); }
.menu-pied{ margin-top:var(--e2); padding-top:var(--e2); border-top:1px solid var(--line); }
.menu-sortie{ color:var(--red-d); }
.menu-sortie svg{ color:var(--red-d); }
.menu-sortie:hover{ background:var(--ag-bg2); }
.menu-sortie:hover svg{ color:var(--red-d); }

/* ── Comptes de l'équipe ────────────────────────────────────────────────── */
.liste-role{ margin:var(--e3) 0 0; padding-left:var(--e6); display:flex;
  flex-direction:column; gap:var(--e2); }
.liste-role li{ font:var(--gr-cour) var(--tx-3)/1.5 var(--sans); color:var(--sub); }
.champ-role{ width:auto; min-width:150px; }
.choix-role{ border:0; padding:0; margin:0; display:grid; gap:var(--e3); }
.choix-role legend{ padding:0; margin-bottom:var(--e3); }
/* Toute la ligne est cliquable : viser un bouton radio de 13 px au doigt, sur
   le banc, est le genre de détail qui fait choisir le mauvais rôle. */
.option-role{ display:flex; align-items:flex-start; gap:var(--e3);
  padding:var(--e4) var(--e5); border:1px solid var(--line2); border-radius:11px;
  cursor:pointer; transition:border-color var(--t), background var(--t); }
.option-role:hover{ background:var(--hover); border-color:var(--line3); }
.option-role:has(input:checked){ border-color:var(--green); background:var(--for-bg2); }
.option-role:has(input:focus-visible){ outline:2px solid var(--green); outline-offset:2px; }
.option-role > span{ display:flex; flex-direction:column; gap:2px; }
.option-titre{ font:var(--gr-fort) var(--tx-4)/1.3 var(--sans); color:var(--ink); }

/* ── Les tables de chiffres ─────────────────────────────────────────────────
   `table{width:100%}` étale les colonnes sur toute la carte. Sur la table
   « Par force » — sept colonnes dans une carte de 827 px — chaque colonne
   reçoit ~112 px pour afficher « 3 » : les nombres flottent si loin les uns des
   autres qu'on ne les compare plus. À 1728 px, ces tables sont ÉTIRÉES, pas
   comprimées, et descendre la police n'y changerait rien.

   Les colonnes numériques se réduisent donc à leur contenu, et la colonne de
   libellé absorbe tout le mou. Les chiffres se regroupent, alignés sur leurs
   unités. */
.tbl-chiffres td, .tbl-chiffres th{ width:1%; white-space:nowrap; }
.tbl-chiffres td:first-child, .tbl-chiffres th:first-child{ width:auto; }

/* ── Les graphiques ────────────────────────────────────────────────────────
   Six hauteurs écrites à la main dans les gabarits. `position:relative` est
   porteur : Chart.js dimensionne son canvas sur le parent positionné. */
.graphe{ position:relative; height:var(--h-graphe); min-width:0; }
.graphe.court{ height:var(--h-graphe-court); }
.graphe.large{ height:var(--h-graphe-large); }
/* `souple` s'étire à la hauteur que la rangée lui donne (flex:1 dans une carte en
   colonne), et son plancher est celui d'un graphique normal et non d'un ruban : le
   canvas étant hors du flux, il n'apporte plus aucune hauteur, donc ce plancher est
   tout ce qui reste quand la carte voisine est courte. À 110 px les barres
   devenaient illisibles sur une saison vide. */
.graphe.souple{ height:auto; min-height:var(--h-graphe); }
/* Le canvas SORT DU FLUX, et ce n'est pas une préférence de mise en page : c'est
   ce qui empêche une boucle de redimensionnement sans fin.

   Chart.js écrit la taille du canvas en attributs ET en style en ligne. htmx, lui,
   rétablit après chaque échange les attributs venus du serveur sur tout élément
   portant un `id` — et les canvas en portent un. Le style de Chart.js est donc
   effacé, le canvas redevient `display:inline`, l'interligne de sa boîte ajoute
   quelques pixels à la hauteur AUTOMATIQUE de `.souple`, le ResizeObserver de
   Chart.js voit le conteneur grandir, agrandit le canvas, et ainsi de suite.

   Mesuré en isolation, une seule navigation, fenêtre de 1500 px : 154 px à une
   seconde, 1006 px à deux, 2742 px à cinq, sans borne, avec débordement horizontal
   de la page. Hors du flux, le canvas ne peut plus rien pousser ; `display:block`
   posé ici et non en ligne survit à l'effacement ; `min-width:0` empêche un
   graphique en `flex:1` d'élargir sa rangée. Les sept canvas de l'app sont des
   enfants directs de `.graphe`, un test le vérifie. */
.graphe > canvas{ display:block; position:absolute; inset:0; }

/* Le tableau du tour des pages remplit sa carte. La cellule de prose portait un
   `max-width` : mesuré, il ne fait rien. CSS 2.2 laisse ce cas indéfini sur une cellule
   de tableau, et le navigateur l'ignore pour le calcul des colonnes : 829 px déclarés
   pour 1005 px rendus. Une règle qui ne s'applique pas est pire qu'absente, elle fait
   croire que le cas est traité. */
.page-texte table{ width:100%; }

/* ── Les journaux : garder l'identite du but sous les yeux ──────────────────
   Mesure a 375 px : la table fait 680 px dans un cadre de 351, donc 52 % visible.
   En defilant vers la droite pour lire les attributs d'un but, on perdait son code
   et sa periode : on lisait des valeurs sans savoir a quel but elles appartenaient.
   Neuf colonnes ne rentreront jamais dans un telephone, mais l'identite peut rester.

   `:not([colspan])` est indispensable : la bande de match et la ligne de detail sont
   des cellules a colspan, et les figer les ferait flotter par-dessus la table.
   Le fond est opaque et suit le survol, sinon la cellule collante laisserait voir le
   texte des colonnes qui passent dessous. */
.tbl-journal td:first-child:not([colspan]),
.tbl-journal tbody th:first-child:not([colspan]),
.tbl-journal thead th:first-child{ position:sticky; left:0; z-index:1;
  background:var(--panel); }
.tbl-journal thead th:first-child{ z-index:2; }
.tbl-journal tbody tr:hover td:first-child:not([colspan]),
.tbl-journal tbody tr:hover th:first-child:not([colspan]){ background:var(--hover); }
/* La bordure droite dit ou s'arrete la colonne figee, et donc qu'il y a a defiler. */
.tbl-journal td:first-child:not([colspan])::after,
.tbl-journal tbody th:first-child:not([colspan])::after,
.tbl-journal thead th:first-child::after{ content:""; position:absolute; inset-block:0;
  right:0; width:1px; background:var(--line); }

/* ── Le bandeau des nouveautés, et le journal des versions ──────────────────
   Un bandeau et non une fenêtre modale : sur le banc entre deux périodes, une
   fenêtre à refermer avant de logger un but est une gêne réelle. Il se pose donc
   dans le flux, sous la barre, et n'empêche rien.

   Le produit a désormais UNE modale, `.modale-voile`, et elle ne sert qu'à
   confirmer une suppression (décision D-13). L'argument ci-dessus n'en est pas
   affaibli : une nouveauté n'a rien d'irréversible, et rien à confirmer.

   Les couleurs sont celles du camp « pour » : une nouveauté est une bonne nouvelle,
   et c'est le vocabulaire que la page parle déjà. Aucune teinte nouvelle. */
.neuf{ background:var(--for-bg2); border-bottom:1px solid var(--for-line); }
/* `--marge-page` et non `--marge-colonne` : ce conteneur est DÉJÀ centré par son
   `max-width` et son `margin:0 auto`. La compensation pleine largeur s'y ajoutait au
   lieu de la remplacer, et le bandeau rentrait de 96 px de plus que la page. */
.neuf-in{ max-width:var(--colonne); margin:0 auto;
  padding:var(--e5) var(--marge-page); }
.neuf-tete{ display:flex; align-items:baseline; flex-wrap:wrap; gap:var(--e2) var(--e4);
  margin-bottom:var(--e3); }
.neuf-cap{ font:var(--gr-fort) var(--tx-1) var(--sans); letter-spacing:.1em;
  text-transform:uppercase; color:var(--green-d); }
.neuf-t{ margin:0; font:var(--gr-titre) var(--tx-6) var(--sans); color:var(--ink); }
/* Aucun plafond de mesure de prose ici, à la demande du client : les notes tenaient
   sur 590 px pendant que le titre et les boutons du même bandeau en occupaient 1079,
   et ce bord droit en escalier se lisait comme un bandeau inachevé. Les trois rangées
   partagent maintenant la largeur de la colonne, comme le reste de l'application. */
.neuf-liste{ margin:0; padding-left:var(--e6); display:flex; flex-direction:column;
  gap:var(--e2); font:var(--gr-cour) var(--tx-4)/1.5 var(--sans); color:var(--sub); }
/* L'étiquette de genre dit d'un coup d'œil si la ligne apporte ou répare. Les trois
   reprennent des paires déjà mesurées ailleurs dans la feuille. */
.neuf-genre{ display:inline-block; margin-right:var(--e2); padding:1px var(--e2);
  border-radius:5px; font:var(--gr-fort) var(--tx-1) var(--sans); text-transform:uppercase;
  letter-spacing:.04em; vertical-align:baseline; }
.g-neuf{ background:var(--for-bg); color:var(--green-d); }
.g-corr{ background:var(--ag-bg); color:var(--red-d); }
.g-mieux{ background:var(--gold-bg); color:var(--gold); }
.neuf-pied{ display:flex; align-items:center; flex-wrap:wrap; gap:var(--e3);
  margin-top:var(--e5); }
.neuf-pied form{ margin:0; }

/* ══ UTILITAIRES ═══════════════════════════════════════════════════════════
   Ce qui reste après les couches précédentes, et rien de plus. La liste est
   FERMÉE : 14 sélecteurs au plus, et un test le vérifie. Ajouter une classe
   exige d'en retirer une, ou de prouver dix absorptions. Sans ce plafond, douze
   classes deviennent trois cents en un an et on a réécrit un framework à la
   main, sans documentation et sans étape de construction pour l'élaguer.

   AUCUN NOM NE CONTIENT DE CHIFFRE. C'est la serrure de tout le chantier : il
   n'existe pas de `.mb-14`, pas de `.gap-11`, pas de `.p-16-20`. Une valeur
   hors échelle devient INEXPRIMABLE, ce qui est strictement plus fort
   qu'interdite — il n'y a rien à taper. Le rythme appartient aux conteneurs,
   les densités aux jetons de rôle, les variantes aux mots.

   SPÉCIFICITÉ SIMPLE (0,1,0), JAMAIS DE SÉLECTEUR DOUBLE. Une couche à (0,2,0)
   placée ici, en fin de fichier, surclasserait tout le bloc
   @media (pointer:coarse) qui la précède — y compris `.seg.seg-mini`, de même
   poids mais antérieur en source. La consigne de ne pas toucher au responsive
   serait respectée à la lettre et violée en pratique : la cible tactile
   mourrait en silence.

   Familles volontairement ABSENTES : width, height, background, border,
   position, grid. Une largeur en dur est une décision de mise en page, pas une
   décoration — elle appartient à un composant. */
.ta-r{ text-align:right; }                /* 92 déclarations */
.ta-c{ text-align:center; }               /* 62 */
.fl{ display:flex; }                      /* 79 */
.centre{ align-items:center; }            /* 31 */
.entre{ justify-content:space-between; }  /* 12 */
.enroule{ flex-wrap:wrap; }               /* 14 */
.grandit{ flex:1; }                       /* 23 */
.insecable{ white-space:nowrap; }         /* 24 */
.gras{ font-weight:var(--gr-fort); }      /* 34 */
/* La rampe d'encre, du plus lu au plus effacé. Nommée par sa place dans la
   rampe et non par son jeton : le jour où --sub change de valeur, le nom reste
   vrai. Le vert et le rouge n'y sont pas — `.v-pour` et `.v-contre` les
   portent déjà, parce qu'une encre de camp n'est pas une nuance de gris. */
.encre-forte{ color:var(--ink2); }        /* 26 */
.encre-lue{ color:var(--sub); }           /* 45 */
.encre-discrete{ color:var(--mut); }      /* 33 */
/* `.encre-effacee` quitte --mut3, qui rendait ces 21 emplois a 2,87:1 sur du texte
   de 12 px. Les deux derniers echelons sont desormais proches (5,56 et 5,27 sur
   --panel) et c'est assume : au-dessus de 4,5:1, le theme jour n'offre pas la place
   pour cinq niveaux d'encre. Mieux vaut une gradation discrete et lisible qu'un
   echelon spectaculaire et illisible. La nuit garde toute sa marge. */
.encre-effacee{ color:var(--mut2); }      /* 21 */
/* ══ FIN UTILITAIRES ══════════════════════════════════════════════════════ */

/* `.pagetitle` déclare 28 px depuis toujours ; cette règle les redescendait à 25
   parce qu'elle avait été ajoutée le jour où le titre est devenu un vrai <h1>,
   pour que le rendu ne bouge pas. Le titre de page reprend donc la taille que la
   feuille lui donnait déjà, et l'écart avec un titre de carte redevient lisible. */
h1.pagetitle{ font-family:var(--sans); font-weight:var(--gr-titre); font-size:var(--tx-8);
  line-height:1.15; letter-spacing:-.028em; margin:3px 0 0; }

/* Messages et états vides. La connexion, elle, vit dans la couverture et porte
   les classes de vitrine.css. */
/* Pas de marge propre : le hibou de .page l'espace déjà. Les deux ensemble
   donnaient gouttière + 10 px au seul endroit de la page qui porte un message. */
.msg{ padding:10px 13px; border-radius:9px; font:var(--gr-app) var(--tx-4) var(--sans); }
.msg.error{ background:var(--ag-bg); color:var(--red-d); }
.empty{ text-align:center; color:var(--mut2); padding:var(--e8); font:var(--gr-cour) var(--tx-4) var(--sans); }
/* L'action d'un état vide. Un état vide qui ne propose rien laisse devant une impasse :
   le journal des buts contre en était là, et c'est le premier écran d'une saison qui
   commence. Quand un filtre est en cause, l'action est de le retirer, pas de saisir. */
.empty .pile-action{ margin-top:var(--e5); }

/* ---------------------------------------------------------------------------
   Responsive — le desktop (>1100px) est INCHANGÉ ; en dessous, les grilles se
   replient, la topbar se compacte, les tables défilent, les cibles tactiles
   grossissent. Usage réel : tablette derrière le banc, téléphone dans le bus.
--------------------------------------------------------------------------- */
@media (max-width:1100px){
  .grid.cols-kpi{ grid-template-columns:repeat(3,1fr); }
  .grid.cols-rec{ grid-template-columns:repeat(4,1fr); }
  .grid.cols-pod{ grid-template-columns:repeat(auto-fit,minmax(185px,1fr)); }
  .grid.cols-3, .grid.cols-ctx{ grid-template-columns:1fr 1fr; }
  .vlist{ column-count:1; }
}
@media (max-width:900px){
  /* Le retrait se resserre pour TOUT LE MONDE en meme temps. Il vivait a trois
     endroits et changeait a deux points de rupture differents : sous 375 px la page
     etait a 12, la barre du haut a 14 et le bandeau des nouveautes a 26, donc trois
     verticales pour un seul bord gauche. Le jeton les tient ensemble. */
  :root{ --marge-page:14px; }
  .topbar{ display:flex; flex-wrap:wrap; justify-content:space-between; gap:10px;
    padding:10px var(--marge-page); }
  /* Le filigrane de patinoire est dessine dans un viewBox de 1600x900 et pose en
     `slice` : sur un ecran de 375x812 il est agrandi pour couvrir, et il n en reste
     que deux ou trois traits qui barrent les cartes au hasard. Ce n est plus une
     patinoire, c est une rayure. Il se retire donc sous 900 px, ou le rapport de
     l ecran ne peut pas le porter. La couverture publique, elle, garde le sien :
     elle est plein ecran et le montre en entier. */
  .rk-fond{ display:none; }
  /* `justify-content:center`, herite de la version bureau, est un piege sur un
     conteneur qui defile : le contenu trop large deborde des DEUX cotes, et le
     depassement de gauche est INATTEIGNABLE puisqu un scrollLeft ne peut pas etre
     negatif. Mesure a 375 px : nav de 559 px pour 347 visibles, scrollLeft a 0 et
     l onglet actif a -42 px. Les premiers onglets n etaient donc pas seulement
     coupes, ils etaient hors de portee du doigt. Le contenu part maintenant du bord,
     et tout devient accessible par defilement.
     `scroll-padding-inline` laisse respirer l onglet qu ux.js amene au centre. */
  /* Le ruban devient un panneau. Il ne défile plus : il montre ses dix destinations
     d'un coup, ce qu'un ruban de 559 px dans 347 visibles ne pourra jamais faire.

     `display:none` par défaut et non `x-show` : l'état vient d'une classe, donc il
     survit au rétablissement d'attributs de htmx, et le panneau part fermé après
     chaque navigation, qui est précisément l'état voulu. */
  .nav-declencheur{ display:inline-flex; }
  .nav{ display:none; position:absolute; top:calc(100% + 6px); left:var(--e5);
    right:var(--e5); z-index:40; flex-direction:column; gap:2px; padding:var(--e2);
    border:1px solid var(--line2); border-radius:13px; background:var(--panel);
    box-shadow:0 18px 40px -18px rgba(var(--ombre),.34), 0 2px 6px rgba(var(--ombre),.06); }
  .nav.ouvert{ display:flex; }
  /* Dans un panneau, un onglet est une ENTRÉE : pleine largeur, alignée à gauche,
     et taillée pour le doigt. */
  .nav .nav-tab{ min-height:44px; display:flex; align-items:center;
    padding-inline:var(--e4); border-radius:9px; }
  .nav::-webkit-scrollbar{ display:none; }
  .brand-sub{ display:none; }
  .grid.cols-2, .grid.cols-2a{ grid-template-columns:1fr; }
  [id]{ scroll-margin-top: 110px; }
}
@media (max-width:768px){
  /* Dernier resserrement, et toujours par le jeton : la barre du haut et le bandeau
     suivent sans qu on ait a y penser. */
  :root{ --marge-page:12px; }
  .page{ padding:14px var(--marge-page) 56px; }
  .grid.cols-4{ grid-template-columns:1fr 1fr; }
  .detail-grid{ grid-template-columns:repeat(2,1fr); }
  #toast-root{ width:min(92vw,560px); }
  .toast{ max-width:100%; }
}
@media (max-width:560px){
  /* Voir base.html : le libelle se replie, le « + » et aria-label restent. La barre
     retombe de trois rangees a deux, et rend 47 px de hauteur a chaque page. */
  .topbar .btn-primary .btn-mot{ display:none; }
  /* Replié, le bouton n'entoure plus qu'un « + » : le rembourrage seul le laissait
     à 38 px de large, et un carré de 44 est la cible que le reste de la barre tient.
     Le plancher, et pas plus de rembourrage : la barre est comptée au pixel ici. */
  .topbar .btn-primary{ padding-inline:var(--e5); min-width:44px; }
  .grid.cols-kpi, .grid.cols-rec{ grid-template-columns:1fr 1fr; }
  /* Une seule colonne : les cartes de ces deux grilles portent des tables et des
     listes dont la largeur minimale ne se comprime pas. À deux colonnes sur un
     téléphone, c'est la page entière qui se mettait à défiler de côté. */
  .grid.cols-3, .grid.cols-ctx, .grid.cols-4, .form-grid{ grid-template-columns:1fr; }
  .detail-grid{ grid-template-columns:1fr; }
  /* La zone de dépôt de l'import : son libellé est insécable et le champ de fichier
     porte une largeur intrinsèque de 238 px que rien ne comprime. Les deux côte à
     côte donnaient un minimum de contenu de 431 px, et comme `1fr` vaut
     `minmax(auto, 1fr)`, c'est la COLONNE de la grille qui cédait — 431 px dans un
     conteneur de 351, donc la page Données défilait de côté sur téléphone.

     Empiler rend au champ 287 px au lieu de 158 : la personne lit le nom du fichier
     qu'elle vient de choisir, ce qui est tout l'intérêt du contrôle. */
  /* Les deux libellés nommés s'AJOUTENT au sélecteur au lieu de le remplacer. En le
     restreignant à `.zone-depot`, on croyait ne viser que deux libellés : `label.fl.centre`
     en attrape cinq, et les trois autres, dans le panneau de saisie, auraient perdu leur
     repli sans que rien ne le dise. */
  label.fl.centre, .zone-depot, .case-bandeau{ flex-wrap:wrap; }
  .fichier{ width:100%; }
  .kpi-hero .v{ font-size:32px; }
  /* !important : tous les .vlbl rendus reçoivent un flex-basis en ligne (140 à
     200 px selon le gabarit) qui battrait cette règle, et comme .vlbl est en
     flex-shrink:0, ces largeurs sont des planchers qui font déborder la carte. */
  .vlbl{ flex-basis:118px !important; font-size:12px; }
}
/* Cibles tactiles : uniquement sur écran tactile, la densité desktop reste intacte */
@media (pointer:coarse){
  .nav-tab{ padding:11px 13px; }
  /* Le sélecteur doit reprendre `.ubloc .nav-tab` : une media query n'ajoute pas
     de spécificité, et la règle ci-dessus était battue par la densité du coin
     utilisateur, qui gardait des cibles de 35 x 31 px sur tablette. */
  .ubloc .nav-tab{ padding:11px 13px; min-width:44px; min-height:44px; justify-content:center; }
  /* L'avatar garde ses 28 px pour ne pas faire grandir la pilule .ubloc ; seule
     sa zone de frappe s'étend, sans mordre sur le bouton de langue voisin. */
  .avatar{ position:relative; }
  .avatar::after{ content:""; position:absolute; inset:-6px; }

  /* ── Les cibles que ce bloc n'atteignait pas ─────────────────────────────
     Mesuré à 375 px : `.action-ligne` fait 17 x 10 px, sous les 24 x 24 exigés par
     WCAG 2.5.8 AA. Et ce sont les commandes ✎ et ✕ du sommaire des matchs, donc
     l'une des deux SUPPRIME un match : la rater ou la toucher par accident sont
     deux échecs, et le second est le pire.

     Une zone de 44 px centrée sur chaque glyphe se chevaucherait avec sa voisine,
     et le clic destructeur partirait à la place du clic d'édition. L'élargissement
     est donc vertical par pseudo-élément, qui n'ajoute aucun pixel à la hauteur de
     ligne, et horizontal par rembourrage, qui n'en ajoute pas non plus puisqu'il
     reste sous la hauteur de la boîte de ligne. */
  .action-ligne{ position:relative; padding-inline:var(--e3); }
  .action-ligne::after{ content:""; position:absolute; inset-block:-13px; inset-inline:0; }

  /* Les liens de score et d'adversaire des tables denses entourent un nombre d'un
     ou deux caractères : 17 x 7 px au plus étroit. Deux liens de score se touchent
     dans la même cellule (`3–2`), donc l'élargissement reste VERTICAL : une zone
     étendue latéralement volerait le clic de son voisin. */
  .table-dense tbody a{ position:relative; }
  .table-dense tbody a::after{ content:""; position:absolute; inset-block:-11px; inset-inline:0; }

  /* Le tour des pages de l'Aide : neuf liens de 18 px de haut dans des cellules de
     prose, où la hauteur de ligne peut grandir sans gêner personne. */
  .page-texte td a, .page-texte th a{ display:inline-block; min-height:24px; }
  .chip{ padding:10px 13px; font-size:14px; }
  .seg{ padding:12px; }
  /* À redéclarer ici : .seg.seg-mini (0,2,0) bat le .seg de cette media query,
     qui n'ajoute aucune spécificité. Sans cette ligne, la variante compacte
     annulerait l'agrandissement tactile au lieu de le suivre. */
  .seg.seg-mini{ padding:11px 13px; font-size:13px; }
  .field{ padding:12px; min-height:44px; }
  /* Un PLANCHER, et pas seulement du rembourrage. Trois familles de boutons
     portent `border:none` et perdaient donc les 2 px que sa bordure donne au
     bouton fantôme : mesuré à 375 px, tous les `.btn-primary` faisaient 42 et
     « Vider la saison » 40, sous les 44 que le reste de l'application tient.

     Le pire cas venait du repli du libellé : `.btn-mot` masqué et le `+` porté
     à `line-height:0` laissaient DEUX enfants de hauteur nulle, et « Logger un
     but » — la commande principale — tombait à 24 px, la hauteur de son seul
     rembourrage. Un plancher rend ce cumul impossible quel que soit ce que le
     bouton contient. Le centrage sert la forme repliée, où il ne reste qu'un
     glyphe dans une boîte plus haute que lui. */
  .btn-primary, .btn-ghost, .btn-danger{ padding:12px 20px; min-height:44px;
    min-width:44px; justify-content:center; }
  .rowtoggle{ min-width:44px; min-height:44px; font-size:21px; }
  /* 15 px de haut mesures, seule cible du produit sous le seuil de 24 px de WCAG
     2.5.8 AA, et elle ouvre la creation d'un adversaire. Un bouton est en
     inline-block et centre son contenu verticalement, donc le plancher seul suffit.
     Effet voulu par-dessus : les deux boutons de copie de clip des journaux portent
     la meme classe et mesuraient 21 px, donc echouaient aussi. */
  .linklike{ min-height:44px; }
  /* Le plancher de 44 px etait pose sur `.ubloc .nav-tab`, le coin utilisateur, mais
     pas sur `.nav-tab` nu : la fleche de rafraichissement du panneau de saisie
     rendait 38 px de large. Elle porte donc sa propre classe, plutot que d'elargir
     `.nav-tab`, qui sert aussi le ruban d'onglets de la barre du haut. */
  .action-panneau{ min-width:44px; min-height:44px; }
}
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{ animation-duration:.01ms !important; transition-duration:.01ms !important; }
}

/* Les actions au pied d'une ligne de détail dépliée. */
.actions-detail{ margin-top:var(--pile); }
/* Une carte qui porte une action irréversible se signale par son trait. */
.carte-danger{ border-color:var(--ag-line); }
/* Un bouton seul dans une pile n'a pas à s'étirer sur toute sa largeur. */
.btn-seul{ align-self:flex-start; }
/* Les petites actions collées à la fin d'une cellule : modifier, supprimer. */
.action-ligne{ margin-left:var(--e1); }
