/* ===================================================================
   CLUBORD v2 — LA COUCHE DE MOUVEMENT

   Une application de club s'ouvre trente secondes, entre deux séries, sur
   un téléphone posé sur un banc. Le mouvement n'est pas là pour décorer :
   il sert à trois choses, et à rien d'autre.

     1. DIRE QUE C'EST ARRIVÉ. On répond « présent », on déclare un
        versement, on s'engage à conduire. Sans retour visuel immédiat, on
        appuie une deuxième fois « pour être sûr ».
     2. FAIRE ENTRER LE CONTENU. Une liste qui apparaît d'un bloc semble
        surgir ; la même en cascade semble arriver. Le temps de chargement
        est identique, la sensation ne l'est pas.
     3. MARQUER CE QUI COMPTE. Une victoire n'est pas une ligne de plus.

   ET SURTOUT, TROIS RÈGLES QUI DISENT OÙ NE PAS EN METTRE.

   — Le mouvement ne retarde JAMAIS l'action. L'écran répond tout de suite,
     le réseau confirme après. Une animation qu'il faut attendre est un
     ralentissement déguisé en soin.
   — Rien ne bouge sur un geste répété. Un coach qui pointe vingt joueurs
     fait vingt fois le même clic : la vingtième célébration est une
     insulte. Le retour y est court et discret.
   — Rien ne bouge sous les yeux de quelqu'un qui lit. Les cascades ne se
     jouent qu'au CHANGEMENT D'ÉCRAN, jamais à chaque redessin — sinon
     filtrer une liste la ferait sautiller à chaque frappe.

   ⚠️ La garde « prefers-reduced-motion » est UNIQUE et vit en bas de ce
   fichier. Les anciennes gardes étaient éparpillées dans styles.css, une
   par composant : toute animation ajoutée ensuite y échappait en silence.
   Ici, tout ce qui bouge est couvert par construction.
   =================================================================== */

:root {
  /* Trois durées, pas davantage. Au-delà, personne ne sait plus laquelle
     choisir et l'ensemble perd son unité. */
  --mv-vif:    140ms;   /* un retour tactile : on doit le sentir, pas le voir */
  --mv-normal: 240ms;   /* une entrée, une bascule d'état */
  --mv-ample:  420ms;   /* une célébration, un compteur qui monte */

  /* Sortie franche : rapide au départ, qui se pose à l'arrivée. C'est la
     courbe déjà utilisée par les feuilles de l'app — on ne va pas en
     inventer une seconde. */
  --mv-sortie: cubic-bezier(.22,.9,.24,1);
  --mv-rebond: cubic-bezier(.34,1.56,.64,1);
}

/* ---------- 1. L'entrée en cascade ---------- */
/* Le décalage est porté par une variable posée en JS : le CSS ne sait pas
   compter les frères et sœurs au-delà de nth-child, et écrire quarante
   règles nth-child serait illisible. */
@keyframes mvEntree {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

.mv-entree {
  animation: mvEntree var(--mv-normal) var(--mv-sortie) both;
  animation-delay: var(--mv-retard, 0ms);
}

/* ---------- 2. Le retour sur une action ---------- */
/* Une pression doit s'enfoncer. C'est le retour le moins coûteux qui
   existe et il s'applique partout, sans une ligne de JavaScript. */
.btn:active,
.rg-seg button:active,
.poste-icon-btn:active,
.bottom-nav button:active,
.hdr-avatar:active {
  transform: scale(0.965);
  transition: transform 60ms ease-out;
}

.btn, .rg-seg button, .poste-icon-btn, .hdr-avatar {
  transition: transform var(--mv-vif) var(--mv-sortie);
}

/* La confirmation d'un enregistrement : une pulsation verte, très brève.
   Discrète par choix — elle se joue à chaque présence pointée. */
/* ⚠️ UN ANNEAU QUI S'ESTOMPE NE SE VOIT PAS (corrigé le 03/10/2026).
   Première version : une onde verte de 9 px autour du bouton. Sur un
   écran de téléphone, dans un gymnase, en tenant l'appareil d'une main —
   invisible. Signalé en vidéo : « pas assez visible ».

   Trois choses ensemble, et il en faut trois : le bouton se remplit
   brièvement de vert (une teinte posée EN INSET, donc par-dessus
   n'importe quel fond, dégradé compris), il grossit d'un poil, et l'onde
   part plus loin. Toujours court — un coach pointe vingt joueurs à la
   suite, et une célébration d'une seconde par joueur serait un supplice. */
@keyframes mvConfirme {
  0% {
    transform: scale(1);
    box-shadow: 0 0 0 0 rgba(51,209,122,.6), inset 0 0 0 999px rgba(51,209,122,.42);
  }
  28% {
    transform: scale(1.055);
    box-shadow: 0 0 0 7px rgba(51,209,122,.3), inset 0 0 0 999px rgba(51,209,122,.42);
  }
  100% {
    transform: scale(1);
    box-shadow: 0 0 0 16px rgba(51,209,122,0), inset 0 0 0 999px rgba(51,209,122,0);
  }
}

.mv-confirme { animation: mvConfirme 520ms cubic-bezier(.2, .85, .3, 1);
  /* La teinte est posée en inset : sans ça elle serait rognée par le
     fond du bouton sur les navigateurs qui peignent le dégradé après. */
  position: relative; z-index: 1; }

/* L'inverse, quand ça échoue. Rouge et latéral : un refus se secoue, il
   ne pulse pas — le geste doit être immédiatement lisible comme un non. */
@keyframes mvRefus {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-5px); }
  40%      { transform: translateX(5px); }
  60%      { transform: translateX(-3px); }
  80%      { transform: translateX(3px); }
}

.mv-refus { animation: mvRefus 320ms ease-in-out; }

/* ---------- 3. Le coche qui se dessine ---------- */
/* Pour les validations qui comptent — un versement déclaré, une sélection
   publiée — plutôt que la pulsation ordinaire. */
@keyframes mvTrace { to { stroke-dashoffset: 0; } }
@keyframes mvBulle {
  from { opacity: 0; transform: scale(0.7); }
  60%  { opacity: 1; transform: scale(1.06); }
  to   { opacity: 1; transform: scale(1); }
}

.mv-coche {
  position: fixed; z-index: 9999; pointer-events: none;
  width: 74px; height: 74px; border-radius: 50%;
  background: rgba(11,18,32,0.92);
  border: 1.5px solid rgba(51,209,122,0.5);
  display: grid; place-items: center;
  animation: mvBulle var(--mv-normal) var(--mv-rebond) both;
}
.mv-coche svg { width: 36px; height: 36px; }
.mv-coche path {
  fill: none; stroke: var(--c-vert); stroke-width: 3.4;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 32; stroke-dashoffset: 32;
  animation: mvTrace 300ms var(--mv-sortie) 90ms both;
}

/* ---------- 4. La victoire ---------- */
/* Le seul endroit où l'application a le droit d'être exubérante. Un match
   gagné, c'est la raison d'être du club — pas une ligne de tableau. */
@keyframes mvConfetti {
  from { opacity: 1; transform: translate3d(0,0,0) rotate(0deg); }
  to   { opacity: 0;
         transform: translate3d(var(--mv-dx), var(--mv-dy), 0) rotate(var(--mv-rot)); }
}

.mv-confetti {
  position: fixed; z-index: 9998; pointer-events: none;
  width: 8px; height: 12px; border-radius: 2px;
  animation: mvConfetti var(--mv-duree, 1100ms) cubic-bezier(.18,.6,.4,1) both;
}

/* La bannière qui accompagne le lancer. */
@keyframes mvVictoire {
  0%   { opacity: 0; transform: translateY(14px) scale(0.94); }
  18%  { opacity: 1; transform: translateY(0) scale(1); }
  82%  { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-10px) scale(0.98); }
}

.mv-banniere {
  position: fixed; left: 50%; top: 28%; transform: translateX(-50%);
  z-index: 9999; pointer-events: none;
  padding: 14px 26px; border-radius: 16px;
  background: linear-gradient(160deg, rgba(51,209,122,0.22), rgba(11,18,32,0.95));
  border: 1px solid rgba(51,209,122,0.5);
  color: var(--c-titre); font-family: var(--font-disp), sans-serif;
  font-size: 21px; font-weight: 800; letter-spacing: -0.02em;
  text-align: center; white-space: nowrap;
  animation: mvVictoire 2200ms var(--mv-sortie) both;
}
.mv-banniere small {
  display: block; margin-top: 3px; font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-vert);
}

/* ---------- 5. Les barres qui se remplissent ---------- */
/* Une barre d'assiduité à 83 % qui apparaît déjà pleine n'informe pas :
   c'est en la voyant monter qu'on saisit qu'il s'agit d'une proportion. */
.mv-barre { transition: width var(--mv-ample) var(--mv-sortie); }

/* ---------- 6. Le résultat d'un match ---------- */
/* Ces deux classes existaient dans styles.css depuis le début et n'étaient
   posées par aucun code : le score ne pouvait pas être saisi. On les
   redéclare ici avec l'entrée qui va avec, maintenant qu'il le peut. */
@keyframes mvResultat {
  from { box-shadow: 0 0 0 0 rgba(51,209,122,0); }
  50%  { box-shadow: 0 0 22px 2px rgba(51,209,122,0.4); }
  to   { box-shadow: 0 0 0 0 rgba(51,209,122,0); }
}

.ev-card.ev-outcome-win.mv-neuf,
.matchcard.result.win.mv-neuf { animation: mvResultat 1400ms ease-out; }

/* ===================================================================
   LA GARDE — unique, en dernier, et volontairement brutale.

   « Réduire les animations » n'est pas une préférence esthétique : c'est
   un réglage d'accessibilité que posent les personnes sujettes au mal des
   transports ou aux troubles vestibulaires. Un mouvement qu'on a oublié
   d'exclure leur donne la nausée. On coupe donc TOUT par défaut, y compris
   ce qui sera ajouté plus tard, plutôt que d'énumérer.

   Ce qui reste : les changements de couleur et d'opacité, qui ne déplacent
   rien. Une confirmation doit rester visible pour tout le monde.
   =================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .mv-confetti { display: none !important; }

  /* La bannière de victoire reste, sans mouvement : l'information « vous
     avez gagné » ne dépend pas de l'animation, et la supprimer priverait
     ces personnes du seul moment de fête de l'application. */
  .mv-banniere { animation: none !important; opacity: 1; }
}

/* ===================================================================
   LES CARTES D'AGENDA — L'AFFICHE ET LA LIGNE

   Un match est une affiche : deux clubs face à face, celui qui reçoit à
   gauche. La position porte l'information, ce qu'aucun badge ne fait
   aussi vite. Un entraînement tient sur une ligne. C'est le CONTRASTE DE
   FORME qui rend le match reconnaissable sans qu'on ait à lire.
   =================================================================== */

/* .ev-card, dans styles.css, impose display:flex, padding:13px,
   align-items:center et overflow:hidden. Il faut donc DEFAIRE ces quatre
   règles, sinon l'affiche est mise en ligne avec le reste, doublement
   rembourrée, et la bande lumineuse est rognée par overflow:hidden. */
.ev-card.af-carte {
  display: block;
  padding: 0;
  align-items: stretch;
  overflow: visible;
  position: relative;
  border-radius: 14px;
}

/* victoryGlow, dans styles.css, fait pulser une ombre depuis le centre.
   C'est précisément l'effet « phare » qu'on a écarté : la bande sur rail
   le remplace. */
.ev-card.af-carte.ev-outcome-win { animation: none; }

/* Plus d'air qu'à l'origine. Une carte de match porte selon le rôle des
   blocs très différents — pastille de réponse pour un joueur, compteurs
   pour un coach, besoins à pourvoir pour un organisateur. Serrée, elle
   devient illisible pour celui qui en voit le plus. */
.af-dedans { position: relative; padding: 15px 16px 16px; }

.af-tete {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; margin-bottom: 13px;
}
.af-type { font-size: 10px; letter-spacing: 0.09em; font-weight: 800; }
.af-quand { font-size: 11px; color: var(--c-texte-faible); }

.af-affiche { display: flex; align-items: center; gap: 9px; }

.af-cote { flex: 1; display: flex; align-items: center; gap: 7px; min-width: 0; }
.af-droite { justify-content: flex-end; }

.af-nom {
  font-size: 13.5px; color: var(--c-texte-doux); line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Notre club en blanc, l'adversaire en gris : on se repère avant de lire. */
.af-nom-nous { color: var(--c-titre); font-weight: 700; }

.af-logo {
  width: 26px; height: 26px; border-radius: 50%;
  flex: none; object-fit: contain; background: var(--c-bord);
}
.af-init {
  display: grid; place-items: center;
  font-size: 11px; font-weight: 800; color: var(--c-texte-faible);
}
.af-nous { background: rgba(var(--accent-rgb), 0.28); color: var(--c-titre); }

.af-vs { font-size: 10px; color: var(--c-texte-faible); flex: none; }
.af-score {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 16px; color: var(--c-titre); flex: none; letter-spacing: 0.02em;
}

.af-pied {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; margin-top: 13px; padding-top: 11px;
  border-top: 1px solid rgba(255,255,255,0.09);
}
.af-lieu {
  font-size: 11px; color: var(--c-texte-faible);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.af-classement {
  flex: none; font-size: 11px; color: var(--c-vert);
  background: none; border: 0; padding: 2px 0; cursor: pointer;
  font-family: inherit;
}
.af-classement sup { font-size: 8px; }

/* ---------- La bande sur rail ---------- */
/* Le trait est pointillé et c'est son DÉCALAGE qu'on anime : la lumière
   est sur le bord, elle ne traverse jamais la carte. Une version
   précédente faisait tourner un dégradé conique derrière — ça balayait
   toute la face comme un phare.

   pathLength="100" normalise le tracé : la bande fait toujours 14 % du
   contour, quelle que soit la largeur de la carte. Les coordonnées
   réelles sont posées après le rendu par calerBandes(). */
.af-cadre {
  position: absolute; inset: 0; width: 100%; height: 100%;
  overflow: visible; pointer-events: none; z-index: 1;
}
.af-rail { fill: none; stroke: rgba(51,209,122,0.2); stroke-width: 1.5; }
.af-bande {
  fill: none; stroke: var(--c-vert); stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 14 86;
  filter: drop-shadow(0 0 5px rgba(51,209,122,0.85));
  animation: afCourt 1.6s linear infinite;
}
@keyframes afCourt { to { stroke-dashoffset: -100; } }

/* ---------- La carte ordinaire ---------- */
/* EMPILÉE, PAS EN LIGNE. Les blocs de présence sont larges et leur nombre
   dépend du rôle : un joueur voit deux boutons, un coach voit en plus
   deux compteurs et un lien. Sur une rangée horizontale, le titre était
   comprimé jusqu'à se replier derrière eux.

   La règle générale : on dessine pour le rôle qui en voit le PLUS. */
.ev-card.ev-simple {
  display: block;
  padding: 13px 15px 14px;
  border-left: 3px solid var(--c-vert);
  border-radius: 0 12px 12px 0;
  overflow: visible;
}

.es-tete {
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; margin-bottom: 7px;
}
.es-type { font-size: 10px; font-weight: 800; letter-spacing: .09em; }
.es-quand { font-size: 11px; color: var(--c-texte-faible); flex: none; }

.es-titre {
  font-size: 14.5px; color: var(--c-titre); font-weight: 700; line-height: 1.3;
  margin-bottom: 3px;
}
.es-meta { font-size: 11.5px; color: var(--c-texte-faible); line-height: 1.45; }

/* Chaque bloc reprend toute la largeur, et respire. */
.es-bloc { margin-top: 10px; }

@media (prefers-reduced-motion: reduce) {
  /* La bande s'immobilise en contour continu : l'information « gagné »
     ne doit jamais dépendre du mouvement. */
  .af-bande { animation: none !important; stroke-dasharray: none; opacity: 0.55; }
}

/* ---------- Le classement ---------- */
.cl-carte { cursor: pointer; }
.cl-total { font-size: 10.5px; color: var(--c-texte-faible); font-weight: 500; text-transform: none; }

/* L'étoile de mise en avant, dans l'en-tête du classement d'accueil. Elle
   n'apparaît que si la personne a PLUSIEURS classements : sous un
   classement unique, il n'y a rien à arbitrer. */
.cl-etoile {
  border: none; background: transparent; cursor: pointer; padding: 2px 4px;
  font-size: 16px; line-height: 1; color: var(--c-texte-tres-faible);
  transition: transform 0.12s, color 0.15s; font-family: inherit;
}
.cl-etoile:active { transform: scale(0.88); }
.cl-etoile.on { color: var(--gold); }

.cl-entete, .cl-ligne {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px;
}
.cl-entete {
  font-size: 9.5px; font-weight: 800; color: var(--c-texte-faible);
  text-transform: uppercase; letter-spacing: 0.07em; padding-top: 0; padding-bottom: 6px;
}

/* ⚠️ DES LIGNES QUI RESPIRENT, PAS UNE GRILLE.
   Le tableau à filets faisait tache dans le décor : chaque club devient une
   bande arrondie, une sur deux légèrement plus claire, et les chiffres se
   centrent sous leur en-tête. */
.cl-tableau { display: flex; flex-direction: column; gap: 3px; }
.cl-ligne {
  border-radius: 10px; border: 1px solid transparent;
  background: rgba(var(--c-voile), 0.025);
}
.cl-ligne:nth-child(even) { background: rgba(var(--c-voile), 0.05); }

/* Notre équipe surlignée : c'est la seule ligne qu'on cherche vraiment. */
.cl-nous, .cl-nous:nth-child(even) {
  background: rgba(var(--accent-rgb), 0.18);
  border-color: rgba(var(--accent-rgb), 0.45);
}
.cl-nous .cl-nom { color: var(--c-titre); font-weight: 800; }

.cl-rang {
  width: 24px; height: 24px; flex: none; border-radius: 7px;
  display: grid; place-items: center;
  font-size: 11.5px; font-weight: 800; color: var(--c-texte-faible);
  background: rgba(var(--c-voile), 0.05);
  font-variant-numeric: tabular-nums;
}
.cl-entete .cl-rang { height: auto; background: none; }
.cl-podium-1 { background: var(--gold); color: var(--c-carte); }
.cl-podium-2, .cl-podium-3 { background: rgba(var(--gold-rgb), 0.24); color: var(--c-titre); }
.cl-nous .cl-rang { background: var(--accent); color: #fff; }

.cl-logo {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  object-fit: contain; background: var(--c-bord);
}
.cl-logo-tete { width: 24px; flex: none; }
.cl-init {
  display: grid; place-items: center;
  font-size: 10px; font-weight: 800; color: var(--c-texte-faible);
}

.cl-nom {
  flex: 1; min-width: 0; font-size: 12.5px; font-weight: 600; color: var(--c-texte-doux);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cl-entete .cl-nom { font-size: 9.5px; font-weight: 800; color: var(--c-texte-faible); }
.cl-col {
  width: 34px; flex: none; text-align: center;
  font-size: 12px; color: var(--c-texte-faible);
  font-variant-numeric: tabular-nums;
}
.cl-col.cl-plus { color: var(--c-vert); }
.cl-col.cl-moins { color: var(--c-rouge); }
.cl-pts {
  width: 38px; flex: none; text-align: center; padding: 3px 0; border-radius: 8px;
  font-size: 13px; font-weight: 800; color: var(--c-titre);
  background: rgba(var(--accent-rgb), 0.16);
  font-variant-numeric: tabular-nums;
}
.cl-entete .cl-pts { background: none; padding: 0; font-size: 9.5px; color: var(--c-texte-faible); }

/* Gagnés, nuls, perdus : dès que la largeur le permet. Sur un téléphone,
   J, Diff et Pts suffisent — le reste est un toucher plus loin. */
.cl-large { display: none; }
@media (min-width: 760px) { .cl-large { display: block; } }

/* Sur l'accueil on ne montre que rang, club et points : les onze colonnes
   de la fédération ne tiennent pas sur 375 pixels, et le détail est à un
   toucher. */
.cl-compact .cl-col { display: none; }

.cl-releve {
  margin-top: 10px; font-size: 10.5px; color: var(--c-texte-faible); font-style: italic;
}

/* Un match sans adversaire lisible : même forme, bloc central différent. */
.af-solo {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.af-solo-titre {
  font-size: 15px; font-weight: 800; color: var(--c-titre); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Le point rouge d'un événement nouveau ou déplacé. Il remplace la
   notification : une information sans conséquence se consulte.

   Il est HORS DU FLUX, à cheval sur le coin haut-droit. En ligne, il
   décalait le titre et volait de la largeur à un texte déjà à l'étroit —
   sur une carte de 375 pixels, six pixels comptent.

   L'anneau sombre autour le détache du bord quelle que soit la couleur de
   la carte : vert sur une victoire, rouge sur une défaite, bleu sinon. */
.ev-neuf {
  position: absolute; top: -4px; right: -4px; z-index: 3;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--c-rouge-vif);
  box-shadow: 0 0 0 3px var(--fond-carte, var(--c-creux)), 0 0 8px rgba(255,77,77,0.55);
  pointer-events: none;
}

/* La carte simple garde overflow:hidden de styles.css, qui rognerait le
   point posé sur son coin. */
.ev-card.ev-simple { overflow: visible; }

/* « Ostéo présente » sur une carte. Une teinte à part — ni le vert des
   présences, ni l'orange des besoins : ce n'est ni une réponse attendue
   de nous, ni quelque chose à pourvoir. C'est une information. */
.ev-osteo {
  display: inline-block; font-size: 10.5px; font-weight: 700;
  color: var(--c-violet); border: 1px solid rgba(157,123,216,.45);
  background: rgba(157,123,216,.12);
  border-radius: 6px; padding: 3px 9px;
}
.ev-osteo-non { color: var(--c-texte-faible); border-color: var(--c-bord-fort); background: none; }
.af-osteo { margin-top: 10px; }

/* ===================================================================
   LES PETITS MOUVEMENTS (animations.js, 30/09/2026)

   ⚠️ TOUT CE BLOC EST DÉCORATIF, ET DOIT LE RESTER.
   Le HTML est rendu dans son état final ; ces règles ne font que
   rejouer le chemin qui y mène. Si elles ne s'appliquent pas — vieux
   navigateur, feuille non chargée, mouvement réduit — l'écran reste
   exact. Ne jamais mettre ici une règle sans laquelle une information
   deviendrait illisible.
   =================================================================== */

@keyframes anim-entree {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}

/* La gerbe de confettis. Posée sur le body, donc au-dessus de tout et
   hors de portée du redessin — et surtout sans jamais intercepter un
   doigt : elle part précisément au moment où l'on vient de toucher. */
.anim-gerbe { position: fixed; inset: 0; z-index: 950; pointer-events: none; }
.anim-gerbe i {
  position: absolute; display: block;
  animation: anim-confetti 1.05s cubic-bezier(.15, .7, .35, 1) forwards;
}
@keyframes anim-confetti {
  0%   { opacity: 1; transform: translate(-50%, -50%) scale(.6) rotate(0); }
  35%  { opacity: 1; }
  /* La retombée : le confetti part en l'air, puis tombe plus bas qu'il
     n'est monté. Sans ce deuxième temps, la gerbe s'évapore en l'air et
     le geste paraît inachevé. */
  100% { opacity: 0;
         transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy) + 90px))
                    scale(1) rotate(var(--tour)); }
}

/* ⚠️ LE FILET DE SÉCURITÉ. animations.js s'arrête déjà de lui-même sous
   « mouvement réduit », mais une animation posée en CSS ailleurs dans le
   projet pourrait l'oublier. Ici, plus rien ne bouge, quoi qu'il arrive. */
@media (prefers-reduced-motion: reduce) {
  .anim-gerbe { display: none; }
  [style*="anim-entree"] { animation: none !important; }
}

/* La pastille de mouvement au classement (db/180) : elle arrive d'en bas
   et se pose avec un léger dépassement. Assez pour attraper l'oeil qui
   balayait la ligne, trop peu pour la faire sauter. */
@keyframes anim-pastille {
  from { opacity: 0; transform: translateY(6px) scale(.8); }
  to   { opacity: 1; transform: none; }
}

.cl-mouvement { flex: none; margin-left: -4px; margin-right: 2px;
  font-size: 9.5px; font-weight: 900; letter-spacing: -.3px;
  font-variant-numeric: tabular-nums; }
.cl-mouvement.monte   { color: var(--c-vert); }
.cl-mouvement.descend { color: var(--c-rouge); }

/* Le ruban de saison : une case par journée. Elles se partagent la
   largeur — vingt journées ou trente, la bande occupe la même place et
   ne déborde jamais. En dessous de 5 px de large, on ne distingue plus
   les couleurs : la hauteur compense en montant. */
.ste-ruban { display: flex; gap: 3px; align-items: stretch; }
.ste-case { flex: 1 1 0; min-width: 4px; height: 26px; border-radius: 4px;
  background: var(--c-creux); }
.ste-case.win   { background: var(--c-vert); }
.ste-case.draw  { background: var(--c-ambre); }
.ste-case.loss  { background: var(--c-rouge); }
/* Un match passé sans score : la fédération n'a rien publié. Hachuré
   plutôt que gris plein, pour ne pas le confondre avec « à venir ». */
.ste-case.attente { background: repeating-linear-gradient(45deg,
  var(--c-creux) 0 3px, var(--c-bord) 3px 6px); }
.ste-case.avenir { background: var(--c-creux); opacity: .55; }

/* Les anneaux dans la carte d'accueil. Plus petits que ceux du bandeau
   supporter : ici ils accompagnent une carte déjà chargée, ils ne sont
   pas le sujet. */
.nc-ronds { margin-top: 12px; }
.nc-ronds .esu-ronds { justify-content: flex-start; gap: 12px; }
.nc-ronds .esu-rond-bloc { flex: none; width: 42px; }
.nc-ronds .esu-rond-n { font-size: 14px; }
.nc-ronds .esu-rond-lab { font-size: 6.5px; }

/* La lueur de la carte se réveille en approchant. Trois paliers, jamais
   un clignotement : on finirait par le couper, donc par ne plus rien
   voir. */
.next-card.nc-bientot .nc-glow  { animation: nc-souffle 5.5s ease-in-out infinite; }
.next-card.nc-demain .nc-glow   { animation: nc-souffle 3.8s ease-in-out infinite; }
.next-card.nc-imminent .nc-glow { animation: nc-souffle 2.4s ease-in-out infinite; }
@keyframes nc-souffle {
  0%, 100% { opacity: .55; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .next-card .nc-glow { animation: none !important; }
}

/* Le mot de bienvenue du tout premier soir. Il n'apparaît qu'une fois
   dans la vie d'un compte : il a droit au dégradé des relances. */
.acc-bienvenue { margin: 0 0 14px; padding: 16px 18px; border-radius: 18px;
  text-align: center;
  background: linear-gradient(140deg,
    color-mix(in srgb, var(--accent) 16%, var(--c-carte)),
    color-mix(in srgb, var(--c-corail) 12%, var(--c-carte)));
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  animation: anim-entree .5s cubic-bezier(.2, .9, .3, 1) both; }
.acc-bienvenue-titre { font-size: 19px; font-weight: 900; color: var(--c-titre);
  letter-spacing: -.3px; }
.acc-bienvenue-sous { margin-top: 4px; font-size: 12.5px; color: var(--c-texte-doux); }

/* ---------- Le squelette de chargement ----------
   ⚠️ LE SEUL « FAUX » MOUVEMENT QU'ON S'AUTORISE.
   Il ne montre rien de vrai : il occupe la place que le contenu prendra.
   On l'accepte parce qu'il supprime une gêne réelle — l'écran qui saute
   quand les données arrivent — et jamais pour faire croire que l'app va
   plus vite qu'elle ne va. */
.sq { border-radius: 10px; background: var(--c-creux); position: relative;
  overflow: hidden; }
.sq::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 0%,
    color-mix(in srgb, var(--c-texte-faible) 13%, transparent) 50%, transparent 100%);
  transform: translateX(-100%);
  animation: sq-chatoie 1.35s ease-in-out infinite; }
@keyframes sq-chatoie { to { transform: translateX(100%); } }
.sq-ligne { height: 13px; margin: 9px 0; }
.sq-titre { height: 19px; width: 55%; margin: 4px 0 12px; }
.sq-carte { height: 92px; margin-bottom: 12px; border-radius: 16px; }
@media (prefers-reduced-motion: reduce) { .sq::after { animation: none; } }

/* La jauge de réponses des cartes d'événement (01/10/2026). Réservée à
   ceux qui suivent les présences — un joueur ne la voit jamais.
   La barre extérieure est la part de gens qui ONT REPONDU : le gris qui
   reste derrière est le silence, et c'est lui que le coach cherche. */
.pres-jauge-ligne { display: flex; align-items: center; gap: 10px;
  margin-top: 9px; flex-wrap: wrap; }
.pres-jauge { flex: 1 1 110px; min-width: 90px; height: 7px; border-radius: 99px;
  background: var(--c-creux); overflow: hidden; display: block; }
/* ⚠️ VERT ET ROUGE FIXES, PAS --c-vert / --c-rouge.
   Même piège que les couleurs d'équipe : le thème du club aplatit les
   jetons sémantiques. Chez un club en Marine, --c-vert vaut #0c6289 —
   un bleu canard. La jauge affichait donc « présents » en bleu marine et
   « absents » en rouge : impossible de lire d'un coup d'oeil ce que la
   barre raconte, ce qui est pourtant sa seule raison d'être. */
.pres-remplie { display: flex; height: 100%; border-radius: 99px;
  background: var(--jauge-non); overflow: hidden; }
.pres-oui { display: block; height: 100%; background: var(--jauge-oui); }
.pres-chiffres { font-size: 11px; color: var(--c-texte-faible); }
.pres-chiffres b { color: var(--c-titre); font-variant-numeric: tabular-nums; }
.pres-muets { color: var(--c-ambre-2); font-weight: 700; }

/* ===================================================================
   LES COULEURS D'ÉQUIPE (app.js, couleurEquipe — 01/10/2026)

   ⚠️ UNE PALETTE À ELLE, ET SURTOUT PAS LES JETONS SÉMANTIQUES.

   Première version : --c-bleu, --c-vert, --c-miel… Erreur. « data-theme »
   porte la PALETTE DU CLUB : chez un club en Marine, --c-vert vaut
   #0c6289, --c-bleu #1d5fa8, --c-miel #2f4f86 et --gold #3d5a80. Huit
   jetons, huit bleus — deux équipes auraient été strictement
   indiscernables, et le tri visuel qu'on cherchait à créer n'aurait
   jamais existé.

   Ces huit teintes-ci sont donc FIXES, choisies pour leur écart de
   teinte (bleu, orange, émeraude, violet, rose, cyan, ambre, ardoise) et
   déclinées en deux jeux : vives sur fond sombre, saturées sur fond
   clair. Le texte de la pastille reste var(--c-carte) dans les deux cas,
   ce qui donne du blanc sur fond clair et du sombre sur fond sombre.

   ⚠️ ET LA COULEUR N'EST JAMAIS SEULE. La pastille porte toujours le nom
   de l'équipe : un daltonien doit pouvoir lire l'agenda entier.
   =================================================================== */
:root {
  --eq-0: #60a5fa; --eq-1: #fb923c; --eq-2: #34d399; --eq-3: #a78bfa;
  --eq-4: #f472b6; --eq-5: #22d3ee; --eq-6: #fbbf24; --eq-7: #94a3b8;
}
:root[data-clair="1"] {
  --eq-0: #2563eb; --eq-1: #ea580c; --eq-2: #059669; --eq-3: #7c3aed;
  --eq-4: #db2777; --eq-5: #0891b2; --eq-6: #ca8a04; --eq-7: #475569;
}

/* Le vert et le rouge de la jauge de réponses, pour la même raison : le
   thème du club ne doit pas pouvoir les repeindre. Présent / absent est
   une convention universelle, elle ne dépend d'aucune identité de club. */
:root { --jauge-oui: #34d399; --jauge-non: #f87171; }
:root[data-clair="1"] { --jauge-oui: #059669; --jauge-non: #dc2626; }

/* La pastille de temps des cartes d'événement (01/10/2026). Elle bat une
   fois par seconde : chiffres tabulaires obligatoires, sinon « 1 » et
   « 8 » n'occupent pas la même place et la tête de carte tremble. */
.ev-tic { flex: none; display: inline-flex; align-items: center; gap: 5px;
  margin-left: 2px; padding: 3px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 900; letter-spacing: -.2px;
  font-variant-numeric: tabular-nums;
  color: var(--gold); background: color-mix(in srgb, var(--gold) 13%, transparent); }
/* Pendant le match, elle prend le rouge du direct — le même que le
   bandeau du supporter, pour que les deux se reconnaissent. */
.ev-tic.en-direct { color: var(--c-rouge-vif);
  background: color-mix(in srgb, var(--c-rouge-vif) 13%, transparent); }

/* Le deuxième temps de « Tu viens ? » : le motif d'absence (03/10/2026).
   Quatre choix rapides et un champ libre — la base exige une
   justification pour une absence déclarée sur soi-même, et on ne va pas
   envoyer quelqu'un la taper dans un autre écran. */
.tuv-motifs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  margin: 2px 0 10px; }
.tuv-motif { padding: 11px 8px; border-radius: 12px; font-family: inherit;
  font-size: 13px; font-weight: 700; cursor: pointer;
  color: var(--c-texte-doux); background: var(--c-creux);
  border: 1px solid transparent; }
.tuv-motif.actif { color: var(--c-titre); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent); }
.tuv-libre { width: 100%; box-sizing: border-box; margin-bottom: 12px;
  padding: 11px 13px; border-radius: 12px; font-family: inherit; font-size: 13px;
  color: var(--c-titre); background: var(--c-creux);
  border: 1px solid var(--c-bord); }
.tuv-libre::placeholder { color: var(--c-texte-tres-faible); }
.tuv-libre:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

/* L'historique des versements tranchés, dans l'onglet « À valider »
   (03/10/2026). Une pastille d'état à gauche : on balaie la colonne et on
   voit d'un coup ce qui a été reçu et ce qui a été refusé. */
.cn-tranche { display: flex; align-items: center; gap: 11px; padding: 10px 0;
  border-bottom: 1px solid var(--c-bord-doux); }
.cn-tranche:last-of-type { border-bottom: 0; }
.cn-tranche-etat { flex: none; font-size: 9.5px; font-weight: 900; letter-spacing: .5px;
  text-transform: uppercase; padding: 4px 9px; border-radius: 8px; }
.cn-tranche-etat.oui { color: var(--jauge-oui);
  background: color-mix(in srgb, var(--jauge-oui) 14%, transparent); }
.cn-tranche-etat.non { color: var(--jauge-non);
  background: color-mix(in srgb, var(--jauge-non) 14%, transparent); }
.cn-tranche-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }

/* ===================================================================
   LES FICHES PRENNENT LE VISAGE DES FENÊTRES (03/10/2026)

   Demandé tel quel : « que les ouvertures de toutes les cartes aient le
   même design que les nouveaux popup ».

   ⚠️ LE MÊME LANGAGE, PAS LE MÊME OBJET — ET C'EST VOLONTAIRE.

   Une fenêtre de relance pose UNE question et porte UNE action : elle est
   petite, centrée, et n'a pas de croix (« une croix se touche sans
   lire »). Une fiche d'événement porte les présences, la composition, le
   covoiturage, le score : elle est haute et elle défile. Lui imposer le
   gabarit d'une fenêtre reviendrait à mettre huit écrans dans une boîte
   de 360 px.

   Ce qui est donc repris : le voile, le halo, l'arrondi, l'ombre, le
   ressort à l'ouverture et le bouton principal. Ce qui ne l'est pas : la
   taille, le défilement, et la croix — qui reste, parce qu'une fiche
   qu'on ne peut pas refermer est un piège.

   ⚠️ ET TOUT SE JOUE ICI, EN CSS. Les 60 fiches de l'application
   partagent déjà .sheet / .sheet-hero / .sheet-cta : on n'a pas touché à
   une seule fonction de rendu. C'est aussi ce qui rend ce changement
   réversible d'un bloc.
   =================================================================== */

/* Le voile : exactement celui des fenêtres. */
.sheet-overlay .sheet-scrim,
.modale-overlay .modale-scrim { background: rgba(3, 8, 20, .74); backdrop-filter: blur(4px); }

.sheet-overlay .sheet,
.modale-overlay .modale {
  border-radius: 26px;
  border: 1px solid var(--c-bord);
  box-shadow: 0 28px 70px rgba(0, 0, 0, .5), 0 0 0 1px rgba(255, 255, 255, .03) inset;
  /* Le même ressort que les fenêtres : il dépasse légèrement puis se
     pose. C'est ce qui fait qu'une fiche « arrive » au lieu d'apparaître. */
  animation: emb-monte .28s cubic-bezier(.18, .9, .26, 1.08);
}

/* ⚠️ LE BANDEAU PERD SON DÉGRADÉ PLEIN POUR LE HALO DES FENÊTRES.
   L'ancien ::before peignait var(--grad) à 50 % sur toute la surface :
   une barre de couleur en haut de chaque fiche. Le halo, lui, est une
   lueur qui déborde du coin et se dissout — c'est ce qui donne aux
   fenêtres leur impression de profondeur, et c'est reproductible sur
   n'importe quelle hauteur de contenu. */
.sheet-overlay .sheet-hero { border-radius: 26px 26px 0 0; padding: 22px 20px 16px;
  isolation: isolate; }
.sheet-overlay .sheet-hero::before {
  inset: -70px 0 -20px -40px;
  background:
    radial-gradient(150px 130px at 120px 95px,
      color-mix(in srgb, var(--accent) 62%, transparent) 0%, transparent 72%),
    radial-gradient(170px 140px at 92% 40px,
      color-mix(in srgb, var(--c-corail) 34%, transparent) 0%, transparent 74%);
  opacity: 1;
}
.sheet-overlay .sheet-hero h2 { font-size: 21px; font-weight: 900; letter-spacing: -.4px; }
.sheet-overlay .sheet-hero-eyebrow { font-size: 9px; letter-spacing: .9px; font-weight: 900; }

/* La croix reste — une fiche doit pouvoir se fermer — mais elle prend la
   discrétion des fenêtres plutôt que le rond plein d'avant. */
.sheet-overlay .sheet-close {
  background: color-mix(in srgb, var(--c-creux) 70%, transparent);
  border: 1px solid var(--c-bord); color: var(--c-texte-faible);
}
.sheet-overlay .sheet-close:hover { color: var(--c-titre); }

/* Le bouton principal : celui des fenêtres, dégradé et relief compris.
   C'est le changement le plus visible, parce qu'il y en a un dans
   presque toutes les fiches. */
.sheet-overlay .sheet-cta,
.modale-overlay .sheet-cta {
  padding: 15px 18px; border-radius: 15px; font-size: 14.5px; font-weight: 800;
  background: linear-gradient(145deg, var(--accent), var(--c-corail));
  box-shadow: 0 10px 24px color-mix(in srgb, var(--accent) 32%, transparent);
  transition: transform .12s, box-shadow .12s;
}
.sheet-overlay .sheet-cta:hover { transform: translateY(-1px);
  box-shadow: 0 14px 30px color-mix(in srgb, var(--accent) 40%, transparent); }
.sheet-overlay .sheet-cta:active { transform: translateY(1px); box-shadow: none; }
.sheet-overlay .sheet-cta.secondary {
  background: var(--c-creux); border: 1px solid var(--c-bord);
  color: var(--c-texte-doux); box-shadow: none; }
.sheet-overlay .sheet-cta.secondary:hover { transform: none; box-shadow: none;
  color: var(--c-titre); }

/* ⚠️ IL Y A DEUX SYSTÈMES DE FICHES, PAS UN. Découvert en vérifiant :
   la fiche d'un événement est une .modale, celle d'un paiement une
   .sheet. Même métier, deux habillages nés à deux moments différents.
   On ne les fusionne pas aujourd'hui — 67 appels à reprendre pour un
   gain nul — mais ils reçoivent le MÊME traitement, sinon la moitié des
   fiches aurait changé de visage et l'autre non. */
/* ⚠️ PAS DE « overflow: hidden » ICI — ET C'EST UNE RÉGRESSION QUE J'AI
   POSÉE LE 03/10 POUR CONTENIR LE HALO.
   Le menu « ... » vit DANS cet en-tête : il s'ouvre vers le bas sur 74 px
   et dépassait de 23 px le bord inférieur. Un tiers de « Supprimer »
   était purement et simplement découpé — mesuré, pas suppose.
   Le halo n'a pas besoin de ce découpage : « .modale » a déjà
   « overflow-y: auto », ce qui clippe les DEUX axes en respectant les
   coins arrondis. C'est elle qui contient le débordement. */
.modale-overlay .modale-hero {
  border-radius: 26px 26px 0 0; padding: 22px 20px 16px; position: relative;
  isolation: isolate;
  background: none;            /* le dégradé plein laisse la place au halo */
}

/* ⚠️ ET LE MENU PASSE DEVANT LES PASTILLES.
   Il n'avait aucun z-index. Les pastilles de l'en-tête sont écrites APRÈS
   lui dans le HTML : à égalité, c'est l'ordre du document qui tranche, et
   « Match » se serait posé par-dessus « Modifier » le jour où les deux se
   croisent. */
.modale-overlay .points-menu, .sheet-overlay .points-menu { z-index: 5; }
/* ⚠️ LE HALO DOIT ÊTRE CONCENTRÉ, PAS ÉTALÉ.
   Première version : deux dégradés lâches sur toute la largeur de la
   fiche. Sur 460 px, la lueur se diluait au point d'être invisible —
   mesurée à l'écran, on ne la distinguait pas du fond. Celui des
   fenêtres marche parce qu'il tient dans un rond de 230 px qui déborde
   par le haut. On reprend cette échelle, pas la surface. */
.modale-overlay .modale-hero::before {
  content: ""; position: absolute; left: -40px; right: 0; top: -70px; bottom: -20px;
  z-index: -1;
  background:
    radial-gradient(150px 130px at 120px 95px,
      color-mix(in srgb, var(--accent) 62%, transparent) 0%, transparent 72%),
    radial-gradient(170px 140px at 92% 40px,
      color-mix(in srgb, var(--c-corail) 34%, transparent) 0%, transparent 74%);
}
.modale-overlay .modale-hero h2 { font-size: 21px; font-weight: 900; letter-spacing: -.4px; }

/* ⚠️ LES PASTILLES PASSAIENT SOUS LA CROIX. Signalé le 03/10/2026, et
   mesuré : la croix et le bouton « ⋮ » sont posés en absolu et occupent
   les 80 px de droite sur les 42 premiers pixels de hauteur ; la
   troisième pastille, elle, s'arrêtait à 29 px du bord. « Pas encore
   répondu » passait donc littéralement SOUS les deux boutons — et le
   mot le plus important de l'en-tête était celui qu'on ne pouvait pas
   lire.
   On réserve le couloir des boutons, et le titre le reprend : il est
   plus bas, rien ne le gêne, et l'indenter de 64 px pour rien ferait
   passer une ligne de trop sur un long nom d'équipe. */
.modale-overlay .modale-hero { padding-right: 86px; }
.modale-overlay .modale-hero h2 { margin-right: -66px; }
.sheet-overlay .sheet-hero { padding-right: 56px; }
.sheet-overlay .sheet-hero h2,
.sheet-overlay .sheet-hero p { margin-right: -36px; }

/* ⚠️ ET UNE PASTILLE NE SE COUPE PAS EN DEUX.
   Une fois le couloir réservé, « Pas encore répondu » s'est mis à passer
   à la ligne AU MILIEU de sa propre pastille — deux moitiés de bulle
   l'une sous l'autre. Le texte d'une étiquette tient d'un bloc ou
   descend d'une ligne entière ; il ne se scinde jamais. */
.modale-overlay .modale-hero .badge,
.sheet-overlay .sheet-hero .badge { white-space: nowrap; }
.modale-overlay .modale-hero { line-height: 2; }
.modale-overlay .modale-close {
  background: color-mix(in srgb, var(--c-creux) 70%, transparent);
  border: 1px solid var(--c-bord); color: var(--c-texte-faible); }

/* ⚠️ « add-toggle » EST LE VRAI BOUTON PRINCIPAL DES FICHES.
   On avait d'abord habillé .sheet-cta, qui n'apparaît presque nulle part
   dans les fiches d'événement : à l'écran, rien n'avait changé. Ce sont
   ces boutons-là — Sélection, Composition, Relancer — qu'on touche. */
/* ⚠️ J'AVAIS ÉTEINT CES BOUTONS, ET C'ÉTAIT TROP (03/10/2026).
   En les « calmant » pour qu'ils cessent de crier, je les ai posés sur
   --c-creux : #0d1119, c'est-à-dire presque du noir sur une carte déjà
   sombre. Six boutons de la même couleur que le fond, ça ne fait pas
   calme, ça fait éteint — et plus rien ne dit où toucher.

   La lumière vient d'en haut, comme sur un vrai objet : un dégradé qui
   s'éclaircit vers le sommet, un liseré clair sur l'arête supérieure,
   et une teinte d'accent dans le fond. Le texte remonte en --c-titre.
   C'est ce qui distingue un bouton d'un bloc de texte. */
.sheet-overlay .add-toggle, .modale-overlay .add-toggle {
  border-radius: 14px; padding: 14px; font-size: 12.5px;
  color: var(--c-titre);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--c-bord));
  background: linear-gradient(176deg,
    color-mix(in srgb, var(--accent) 17%, var(--c-carte)) 0%,
    color-mix(in srgb, var(--accent) 7%, var(--c-creux)) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07),
              0 2px 8px rgba(0, 0, 0, .18);
  transition: transform .12s, filter .15s, box-shadow .15s; }
.sheet-overlay .add-toggle:hover, .modale-overlay .add-toggle:hover {
  filter: brightness(1.14);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1),
              0 6px 16px color-mix(in srgb, var(--accent) 22%, transparent); }
.sheet-overlay .add-toggle:active, .modale-overlay .add-toggle:active {
  transform: scale(.985); filter: brightness(1.2); box-shadow: none; }
/* ⚠️ PAS DE « PREMIER BOUTON EN DÉGRADÉ ». J'avais écrit
   « .add-toggle:first-of-type » pour mettre en avant l'action attendue.
   Deux fautes : :first-of-type vise le premier élément DU MÊME TYPE
   (le premier <button>), pas la première classe — et le premier bouton
   d'une fiche est souvent « Statistiques de l'équipe », qui n'est pas
   l'action attendue. La règle ne tombait donc jamais, et le jour où
   elle serait tombée elle aurait mis en avant la mauvaise chose.
   Une fiche porte quatre à six boutons de même rang : les habiller
   pareil est la seule réponse juste. Le dégradé reste pour les
   fenêtres, qui n'ont QU'UNE action. */

@media (prefers-reduced-motion: reduce) {
  .sheet-overlay .sheet, .modale-overlay .modale { animation: none; }
  .sheet-overlay .sheet-cta { transition: none; }
}

/* ---------- La fiche d'un événement, remise en zones (03/10/2026) ----------
   Trois zones au lieu d'une pile : ce qui se LIT d'un coup (quand et où),
   ce qui se CÉLÈBRE (le score), et ce qui se CONSULTE (la grille). */

/* Quand et où : la seule question qu'on se pose en ouvrant une fiche. */
.mo-quand { padding: 14px 15px; border-radius: 16px; background: var(--c-creux);
  margin-bottom: 12px; }
.mo-quand-haut { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.mo-jour { flex: 1; min-width: 0; font-size: 15px; font-weight: 800;
  color: var(--c-titre); letter-spacing: -.2px; }
.mo-heure { flex: none; font-size: 17px; font-weight: 900; color: var(--c-titre);
  font-variant-numeric: tabular-nums; letter-spacing: -.5px; }
.mo-lieu { margin-top: 4px; font-size: 12.5px; color: var(--c-texte-faible); }
.mo-tic { margin-top: 9px; }

/* Le score : la raison pour laquelle on rouvre un match passé. */
.mo-score { margin-bottom: 12px; padding: 16px 15px; border-radius: 16px;
  text-align: center; background: var(--c-creux); border: 1px solid var(--c-bord); }
.mo-score.gagne { border-color: color-mix(in srgb, var(--jauge-oui) 40%, transparent);
  background: color-mix(in srgb, var(--jauge-oui) 9%, var(--c-creux)); }
.mo-score.perdu { border-color: color-mix(in srgb, var(--jauge-non) 32%, transparent); }
.mo-score-mot { font-size: 9.5px; font-weight: 900; letter-spacing: 1px;
  text-transform: uppercase; color: var(--c-texte-faible); }
.mo-score.gagne .mo-score-mot { color: var(--jauge-oui); }
.mo-score-n { margin-top: 4px; font-size: 34px; font-weight: 900; letter-spacing: -1.5px;
  color: var(--c-titre); font-variant-numeric: tabular-nums; line-height: 1; }
.mo-score-mt { margin-top: 5px; font-size: 11px; color: var(--c-texte-faible); }

/* Les faits, deux par ligne. Sur un écran étroit, une seule colonne :
   « Compétition » et son contenu ne tiennent pas dans 160 px. */
.mo-faits { display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--c-bord-doux); border-radius: 14px; overflow: hidden;
  margin-bottom: 4px; }
.mo-fait { background: var(--c-carte); padding: 11px 13px; }
.mo-fait-lib { font-size: 8.5px; font-weight: 900; letter-spacing: .9px;
  text-transform: uppercase; color: var(--c-texte-tres-faible); }
.mo-fait-val { margin-top: 3px; font-size: 13px; font-weight: 700; color: var(--c-texte-doux);
  line-height: 1.3; }
@media (max-width: 360px) { .mo-faits { grid-template-columns: 1fr; } }

/* Sélection et composition, côte à côte (03/10/2026). Ce sont les deux
   étapes de travail d'un coach, dans cet ordre : l'oeil doit comparer
   leurs deux états d'un seul regard, pas lire deux boutons empilés. */
.mo-compos { display: grid; grid-template-columns: 1fr 1fr; gap: 9px;
  margin-top: 14px; }
.mo-compo { display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  padding: 12px 13px; border-radius: 14px; font-family: inherit; cursor: pointer;
  text-align: left;
  border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--c-bord));
  background: linear-gradient(176deg,
    color-mix(in srgb, var(--accent) 15%, var(--c-carte)) 0%,
    color-mix(in srgb, var(--accent) 6%, var(--c-creux)) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07), 0 2px 8px rgba(0, 0, 0, .18);
  transition: transform .12s, filter .15s; }
.mo-compo:hover { filter: brightness(1.12); }
.mo-compo:active { transform: scale(.98); }
.mo-compo-nom { font-size: 12.5px; font-weight: 800; color: var(--c-titre); }
.mo-compo-etat { font-size: 9.5px; font-weight: 900; letter-spacing: .7px;
  text-transform: uppercase; }
/* ⚠️ LE GRIS N'EST PAS UNE ALERTE. Un match de mai n'a rien à reprocher
   en octobre : « à faire » se lit, il ne clignote pas. */
.mo-compo.vide .mo-compo-etat { color: var(--c-texte-tres-faible); }
.mo-compo.encours { border-color: color-mix(in srgb, var(--eq-6) 45%, transparent);
  background: linear-gradient(176deg,
    color-mix(in srgb, var(--eq-6) 15%, var(--c-carte)) 0%,
    color-mix(in srgb, var(--eq-6) 5%, var(--c-creux)) 100%); }
.mo-compo.encours .mo-compo-etat { color: var(--eq-6); }
.mo-compo.ok { border-color: color-mix(in srgb, var(--jauge-oui) 48%, transparent);
  background: linear-gradient(176deg,
    color-mix(in srgb, var(--jauge-oui) 17%, var(--c-carte)) 0%,
    color-mix(in srgb, var(--jauge-oui) 6%, var(--c-creux)) 100%); }
.mo-compo.ok .mo-compo-etat { color: var(--jauge-oui); }

/* ===================================================================
   LES TRANSITIONS DE VUE (transitions.js, 03/10/2026)

   ⚠️ CE BLOC NE S'APPLIQUE QUE LÀ OÙ L'API EXISTE. Firefox et les vieux
   Safari l'ignorent entièrement : ils retrouvent l'application d'hier,
   sans transition, et sans rien de cassé.
   =================================================================== */

/* Le fondu par défaut, raccourci. Un quart de seconde est le maximum
   avant qu'une transition cesse d'être ressentie comme une réponse et
   devienne une attente. */
::view-transition-old(root), ::view-transition-new(root) {
  animation-duration: .2s; animation-timing-function: cubic-bezier(.3, .8, .3, 1);
}

/* ⚠️ LE GLISSEMENT SUIT L'ORDRE DE LA BARRE DU BAS.
   Accueil → Agenda part vers la gauche, Agenda → Accueil revient par la
   droite — comme on tourne des pages. Sans ce sens, les deux gestes se
   ressembleraient et le mouvement n'apprendrait rien. */
:root[data-vt="avant"]::view-transition-old(root) {
  animation: vt-sort-gauche .2s cubic-bezier(.3, .8, .3, 1) both; }
:root[data-vt="avant"]::view-transition-new(root) {
  animation: vt-entre-droite .22s cubic-bezier(.2, .85, .3, 1) both; }
:root[data-vt="arriere"]::view-transition-old(root) {
  animation: vt-sort-droite .2s cubic-bezier(.3, .8, .3, 1) both; }
:root[data-vt="arriere"]::view-transition-new(root) {
  animation: vt-entre-gauche .22s cubic-bezier(.2, .85, .3, 1) both; }

@keyframes vt-sort-gauche  { to { opacity: 0; transform: translateX(-26px); } }
@keyframes vt-entre-droite { from { opacity: 0; transform: translateX(26px); } }
@keyframes vt-sort-droite  { to { opacity: 0; transform: translateX(26px); } }
@keyframes vt-entre-gauche { from { opacity: 0; transform: translateX(-26px); } }

/* La carte qui se déplie en fiche. Le navigateur fait le morphing tout
   seul dès que les deux portent le même nom ; on ne règle que la durée
   et la courbe, pour que ça se pose au lieu de s'arrêter net. */
/* ⚠️ LE NOM N'EST PAS POSÉ ICI, MAIS EN JS (vtRendre).
   Deux éléments portant le même view-transition-name font abandonner la
   transition ENTIÈRE, en silence. Une règle CSS sur « .modale, .sheet »
   en marquerait deux le jour où deux fiches se superposent — le
   classement par-dessus un match, par exemple. Le JS, lui, n'en marque
   qu'une : la dernière du document, c'est-à-dire celle du dessus. */
::view-transition-group(carte-ouverte) {
  animation-duration: .3s; animation-timing-function: cubic-bezier(.2, .9, .26, 1.02); }
/* Le contenu de la fiche arrive en fondu par-dessus la forme qui grandit :
   sans ça, le texte de la carte s'étire pendant toute la transition. */
::view-transition-old(carte-ouverte) { animation: vt-fondu-sortie .14s linear both; }
::view-transition-new(carte-ouverte) { animation: vt-fondu-entree .26s .06s linear both; }
@keyframes vt-fondu-sortie { to { opacity: 0; } }
@keyframes vt-fondu-entree { from { opacity: 0; } }

/* ⚠️ LA FICHE NE REJOUE PAS SON ENTRÉE APRÈS UNE TRANSITION — ET LE
   GARDE-FOU NE PEUT PAS VIVRE SUR LA RACINE.
   Première version : « :root[data-vt] .modale { animation: none } ».
   Elle tombait trop tôt. L'attribut est retiré dès que la transition est
   finie ; à cet instant précis la règle cesse de s'appliquer et
   l'animation d'entrée DÉMARRE, sur une fiche déjà en place. C'est
   exactement le petit saut signalé à l'ouverture, et il était mesurable :
   la transition terminée, animationName valait encore « emb-monte ».
   Le drapeau est donc posé sur la fiche ELLE-MÊME, depuis le JS, pendant
   la transition (transitions.js). Il n'a pas besoin d'être retiré : le
   prochain rendu recrée l'élément. */
.sans-entree, .sans-entree::before, .sans-entree::after { animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation: none !important; }
}

/* ⚠️ LE PETIT SAUT À CHAQUE OUVERTURE DE FICHE (03/10/2026).
   Signalé en vidéo, puis mesuré : les boutons de la barre du bas passent
   de 75 px à 78 px au moment où une fiche s'ouvre — 5 × 3 px, soit
   exactement une largeur de barre de défilement.

   La cause : « html.fige » coupe le défilement de la page pour qu'elle ne
   bouge pas sous la fiche. La barre disparaît, la fenêtre s'élargit de
   15 px, et TOUT le contenu derrière glisse d'un coup. C'est un classique
   du web, invisible sur téléphone — la barre y flotte au-dessus du
   contenu — et bien visible sur ordinateur, là où le défaut a été vu.

   La gouttière est donc réservée en permanence : qu'il y ait une barre ou
   non, la place est prise, et plus rien ne bouge. Un ruban vide de 15 px
   sur les pages trop courtes pour défiler est le prix, et il ne se
   remarque pas. */
html { scrollbar-gutter: stable; }

/* Le point rouge des alertes qui tressaille quand quelque chose tombe,
   app ouverte (alertes.js, 03/10/2026). Une fois, pas un clignotement :
   une pastille qui bat en permanence devient du décor, et on finit par
   ne plus la voir — exactement le contraire du but. */
.hdr-avatar.al-neuf::after, .rail-avatar.al-neuf::after {
  animation: al-tressaut .7s cubic-bezier(.2, .9, .3, 1.3) 2; }
@keyframes al-tressaut {
  0%, 100% { transform: scale(1); }
  30%      { transform: scale(1.75); }
  55%      { transform: scale(.92); }
}
@media (prefers-reduced-motion: reduce) {
  .hdr-avatar.al-neuf::after, .rail-avatar.al-neuf::after { animation: none; }
}

/* Le squelette de l'agenda : des cartes à la bonne forme, le temps que la
   lecture revienne. Les hauteurs sont celles des vraies cartes — un
   squelette plus court que le contenu fait sauter l'écran autant qu'une
   absence de squelette. */
.sq-liste { display: flex; flex-direction: column; gap: 9px; }
.sq-bloc { padding: 14px; }
.sq-bloc .sq-titre { height: 17px; width: 62%; margin: 0 0 11px; }
.sq-bloc .sq-ligne { height: 11px; margin: 7px 0; }

/* ===================================================================
   « ÇA TRAVAILLE » (attente.js, 05/10/2026)

   ⚠️ DISCRÈTE, MAIS BLOQUANTE. Le voile est léger — on voit encore
   l'écran derrière, donc on garde son repère — mais il intercepte les
   clics : c'est ce qui empêche de toucher trois fois « Déclarer » parce
   que rien ne bouge. Toute la valeur de cette fenêtre est là autant que
   dans ce qu'elle affiche.
   =================================================================== */
.att-voile { position: fixed; inset: 0; z-index: 940;
  display: flex; align-items: center; justify-content: center;
  background: rgba(3, 8, 20, .42); backdrop-filter: blur(1.5px);
  animation: att-entre .18s ease-out both; }
.att-voile.att-sort { animation: att-part .16s ease-in both; }
@keyframes att-entre { from { opacity: 0; } }
@keyframes att-part  { to   { opacity: 0; } }

.att-boite { display: flex; align-items: center; gap: 12px;
  padding: 14px 20px; border-radius: 16px;
  background: var(--c-carte); border: 1px solid var(--c-bord);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .45);
  font-size: 13px; font-weight: 700; color: var(--c-titre); }

/* Un anneau qui tourne, pas trois points qui sautent : à cette taille,
   une rotation se lit comme « ça avance », un rebond comme « ça
   hésite ». */
.att-rond { width: 18px; height: 18px; flex: none; border-radius: 50%;
  border: 2.5px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-top-color: var(--accent);
  animation: att-tourne .7s linear infinite; }
@keyframes att-tourne { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .att-voile, .att-voile.att-sort { animation: none; }
  .att-rond { animation: none; border-top-color: var(--accent); }
}

/* ===================================================================
   LE PILOTAGE PAR L'USAGE (pilotage-usage.js, 08/10/2026)
   =================================================================== */

/* L'entonnoir d'activation. Chaque marche porte sa barre : on lit la
   chute entre deux lignes sans avoir à soustraire. */
.pu-marche { margin-bottom: 14px; }
.pu-marche:last-of-type { margin-bottom: 4px; }
.pu-marche-haut { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
.pu-marche-nom { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 700;
  color: var(--c-titre); }
.pu-marche-val { flex: none; display: flex; align-items: baseline; gap: 6px; }
.pu-marche-val b { font-size: 17px; font-weight: 900; color: var(--c-titre);
  font-variant-numeric: tabular-nums; }
.pu-marche-val span { font-size: 10.5px; color: var(--c-texte-faible);
  font-variant-numeric: tabular-nums; }
.pu-piste { display: block; height: 9px; border-radius: 99px; background: var(--c-creux);
  overflow: hidden; }
.pu-piste > span { display: block; height: 100%; border-radius: 99px;
  background: linear-gradient(90deg, var(--accent), var(--c-corail)); }
.pu-marche-quoi { margin-top: 5px; font-size: 10.5px; color: var(--c-texte-faible); }
.pu-marche-quoi b { color: var(--c-rouge-vif); }
/* ⚠️ LA MARCHE QUI FUIT LE PLUS EST LA SEULE MISE EN AVANT. En colorer
   plusieurs reviendrait à n'en désigner aucune. */
.pu-marche.fuite .pu-piste > span {
  background: linear-gradient(90deg, var(--jauge-non), var(--c-corail)); }
.pu-marche.fuite .pu-marche-nom { color: var(--c-rouge-vif); }

/* Une ligne « quelqu'un, quelque part, un nombre » : comptes bloqués,
   clubs qui décrochent. Même forme, parce que c'est le même geste —
   lire un nom et décider d'appeler. */
.pu-ligne { display: flex; align-items: center; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--c-bord-doux); }
.pu-ligne:last-of-type { border-bottom: 0; }
.pu-ligne-nom { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 700;
  color: var(--c-titre); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pu-ligne-n { flex: none; font-size: 11px; font-weight: 900; color: var(--c-rouge-vif);
  padding: 3px 9px; border-radius: 8px;
  background: color-mix(in srgb, var(--c-rouge-vif) 13%, transparent); }

/* Les vingt-quatre heures de la journée. Des barres fines : on cherche
   la forme de la courbe, pas la valeur de chaque heure — celle-ci est
   dans l'infobulle. */
.pu-heures { display: flex; align-items: flex-end; gap: 3px; height: 92px;
  margin-bottom: 4px; }
.pu-heure { flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: flex-end; height: 100%; min-width: 0; }
.pu-heure i { display: block; width: 100%; border-radius: 3px 3px 0 0;
  background: color-mix(in srgb, var(--accent) 55%, transparent); }
.pu-heure.pic i { background: var(--gold); }
.pu-heure em { font-size: 8px; font-style: normal; color: var(--c-texte-tres-faible);
  margin-top: 4px; height: 10px; }

/* Une fonction dont aucun club ne se sert : la barre est vide, donc
   invisible. On marque la place pour que le zéro se voie. */
.pil-piste i.morte { min-width: 3px;
  background: repeating-linear-gradient(45deg,
    var(--c-bord) 0 3px, transparent 3px 6px); }

/* L'activation club par club : un vrai tableau, parce qu'on vient ici
   pour COMPARER des lignes entre elles — un empilement de cartes ne se
   compare pas. */
.pu-tableau { font-variant-numeric: tabular-nums; }
.pu-tete, .pu-rangee { display: grid; grid-template-columns: 1.4fr .6fr .9fr .9fr .9fr;
  gap: 6px; align-items: center; padding: 9px 0;
  border-bottom: 1px solid var(--c-bord-doux); }
.pu-tete { font-size: 8.5px; font-weight: 900; letter-spacing: .8px;
  text-transform: uppercase; color: var(--c-texte-tres-faible); border-bottom-width: 0; }
.pu-tete span + span { text-align: right; }
.pu-rangee { font-size: 12.5px; font-weight: 700; color: var(--c-titre); }
.pu-rangee:last-of-type { border-bottom: 0; }
/* ⚠️ LE NOMBRE AU-DESSUS DU POURCENTAGE, PAS À CÔTÉ.
   Côte à côte, « 34 100 % » passait à la ligne dès 372 px de large et la
   rangée faisait deux hauteurs. Empilés, chaque cellule tient quelle que
   soit la largeur, et la colonne des pourcentages se balaie d'un coup
   d'oeil vertical — c'est exactement ce qu'on vient y chercher. */
.pu-rangee span + span { display: flex; flex-direction: column; align-items: flex-end;
  line-height: 1.15; }
.pu-rangee-nom { display: flex; align-items: center; gap: 4px; min-width: 0; }
.pu-rangee-nom b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 700; }
.pu-rangee em { font-style: normal; font-size: 9.5px; font-weight: 700;
  color: var(--c-texte-faible); }
/* Sous les seuils, le chiffre se signale lui-même : on balaie la colonne
   et on voit où appeler, sans lire les pourcentages un par un. */
.pu-rangee span.faible { color: var(--c-rouge-vif); }
.pu-rangee span.faible em { color: var(--c-rouge-vif); opacity: .75; }
/* ⚠️ L'ÉTOILE EST UN ÉLÉMENT, PAS UN ::after SUR LE NOM.
   Posée sur le nom, elle tombait DANS la boîte tronquée : sur « HBC
   Hoerdt » raccourci en « HBC Hoer… », elle disparaissait avec la fin du
   mot — et le club modèle n'était plus signalé. */
.pu-etoile { flex: none; color: var(--gold); font-size: 10px; }

/* Les tendances : une courbe par mesure, et la variation depuis le
   dernier relevé. C'est la pente qui décide, pas la valeur. */
.pu-tendance { margin-bottom: 13px; }
.pu-tendance:last-of-type { margin-bottom: 0; }
.pu-tendance-haut { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.pu-tendance-nom { flex: 1; min-width: 0; font-size: 12px; font-weight: 700;
  color: var(--c-texte-doux); }
.pu-tendance-val { flex: none; display: flex; align-items: baseline; gap: 6px; }
.pu-tendance-val b { font-size: 15px; font-weight: 900; color: var(--c-titre);
  font-variant-numeric: tabular-nums; }
.pu-tendance-val em { font-style: normal; font-size: 10.5px; font-weight: 900; }
.pu-tendance-val em.haut { color: var(--jauge-oui); }
.pu-tendance-val em.bas { color: var(--jauge-non); }
.pu-courbe { display: flex; align-items: flex-end; gap: 2px; height: 34px; }
.pu-courbe i { flex: 1; min-width: 2px; border-radius: 2px 2px 0 0;
  background: color-mix(in srgb, var(--accent) 50%, transparent); }
.pu-courbe i:last-child { background: var(--gold); }

/* Les incidents. Le genre à gauche en pastille colorée : on balaie la
   colonne et on sépare d'un coup un refus de droits (règle à corriger)
   d'une erreur (code à corriger) — ce ne sont pas les mêmes journées de
   travail. */
.pu-incident { padding: 11px 0; border-bottom: 1px solid var(--c-bord-doux); }
.pu-incident:last-of-type { border-bottom: 0; }
.pu-incident-haut { display: flex; align-items: center; gap: 9px; }
.pu-incident-genre { flex: none; font-size: 8.5px; font-weight: 900; letter-spacing: .6px;
  text-transform: uppercase; padding: 3px 8px; border-radius: 7px;
  color: var(--c-texte-faible); background: var(--c-creux); }
.pu-incident-genre.refus { color: var(--eq-6);
  background: color-mix(in srgb, var(--eq-6) 15%, transparent); }
.pu-incident-genre.erreur { color: var(--jauge-non);
  background: color-mix(in srgb, var(--jauge-non) 15%, transparent); }
.pu-incident-quoi { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 700;
  color: var(--c-titre); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pu-incident-n { flex: none; font-size: 12px; font-weight: 900; color: var(--c-titre);
  font-variant-numeric: tabular-nums; }
.pu-incident-bas { margin-top: 3px; font-size: 10.5px; color: var(--c-texte-faible); }
/* Le message de la base : en chasse fixe, parce qu'on y cherche un nom
   de colonne ou un code d'erreur, pas une phrase. */
.pu-incident-detail { margin-top: 6px; padding: 7px 9px; border-radius: 8px;
  background: var(--c-creux); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10px; line-height: 1.45; color: var(--c-texte-faible);
  overflow-wrap: anywhere; }

/* Le tableau de la lenteur n'a que quatre colonnes, pas cinq. */
.pu-rangee.pu-r4, .pu-tableau .pu-tete:has(+ .pu-r4) {
  grid-template-columns: 1.7fr .7fr .9fr .8fr; }
