/* ProgLabAi — habillage des pages hors programme (login, choix, admin).
 *
 * Ce bloc décrit le MODE SOMBRE, qui est celui d'origine et le seul du site
 * public. Le mode clair vit dans assets/apparence.css, chargée par les seules
 * pages applicatives (voir apparence_active() dans lib/view.php) : elle
 * remplace les neutres ci-dessous et fonce l'accent d'un cran pour qu'il tienne
 * sur du blanc.
 *
 * Trois variables dérivées portent l'accent — --acc, --acc-2, --sur-acc. Elles
 * sont déclarées ici EN REPLI : le site public ne charge pas apparence.css, et
 * sans ces trois lignes ses boutons perdraient leur couleur. Là où apparence.css
 * est chargée, c'est son bloc de mode clair qui parle — il est plus précis. */
:root{
  --bg:#0b0e14;
  --surface:#141922;
  --surface-2:#1c2230;
  --line:#29313f;
  --text:#e8eef7;
  --dim:#a4b0c2;
  --accent:#22d3ee;
  --accent-2:#7c5cff;
  --danger:#ff7a7a;
  --success:#49b783;
  --acc:var(--accent);
  --acc-2:var(--accent-2);
  /* Le texte des boutons. Il valait #06111a, ce qui tenait 6,4 de contraste au
     milieu du dégradé mais retombait à 4,38 sur son extrémité violette — sous
     le seuil de 4,5. Deux crans plus sombre, indiscernables à l'œil, et les
     deux bouts passent. */
  --sur-acc:#000814;
  /* LE PICTOGRAMME. Sa barre était écrite en dur dans le SVG (#cfd8e6) : en
     mode clair elle tombait à 1,28 de contraste sur le fond de page, et le
     logo disparaissait de sa propre bannière. Elle devient un jeton, comme
     tout le reste — assets/apparence.css lui donne sa version foncée. */
  --logo-barre:#cfd8e6;
}
/* Les deux traits du logo passent par les jetons. Les valeurs des attributs
   SVG restent en repli pour qui n'aurait pas cette feuille. */
.mark .lg-barre{ fill:var(--logo-barre); }
.mark .lg-anneau{ stroke:var(--acc); }
/* Ce que le mode clair reprend, en un seul endroit — tout le reste de la
   feuille l'ignore, parce que tout le reste passe par des variables.
   Ne restent ici que les couleurs qui étaient écrites en dur : un halo de fond
   calculé pour du noir, les trois bandeaux d'alerte, une étiquette d'avertisse-
   ment et le cadre de texte des briefs. Elles ne s'appliquent qu'aux pages
   applicatives, seules à charger assets/apparence.css. */
:root[data-theme="clair"] body{
  background:
    radial-gradient(120% 90% at 50% -20%, #dbe6f4 0%, transparent 60%),
    var(--bg);
}
:root[data-theme="clair"] .alert{
  background:#fdeaea; border-color:#f0bcbc; color:#8f2020;
}
:root[data-theme="clair"] .alert.ok{
  background:#e6f6ee; border-color:#a9dcc4; color:#0c5c3c;
}
:root[data-theme="clair"] .alert.info{
  background:#e9f1fb; border-color:#b8cfe9; color:#1d4a7a;
}
:root[data-theme="clair"] .tag.warn{ color:#8a5a06; border-color:#dbb469; }
:root[data-theme="clair"] .brief-out{ background:#f4f7fb; }
/* Une ombre calculée pour du noir sur du noir devient une tache sur du blanc :
   sur fond clair elle est ramenée à un gris bleuté, plus courte. */
:root[data-theme="clair"] .card,
:root[data-theme="clair"] .why-card,
:root[data-theme="clair"] .coach,
:root[data-theme="clair"] .site-pop,
:root[data-theme="clair"] .cookie-banner{
  box-shadow:0 10px 26px -20px #1d2a3d;
}

*{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
html{height:100%;}
body{
  margin:0; min-height:100%;
  font-family:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  background:
    radial-gradient(120% 90% at 50% -20%, #16202e 0%, transparent 60%),
    var(--bg);
  /* Sans ça le dégradé se répète en descendant et laisse une couture. */
  background-repeat:no-repeat;
  background-attachment:fixed;
  color:var(--text);
  display:flex; flex-direction:column;
}

.shell{
  width:100%; max-width:440px; margin:0 auto;
  /* Le bas etait deja protege ; le haut ne l'etait pas. Installe sur l'ecran
     d'accueil, le site s'affiche sous la barre d'etat, et la barre du haut
     — nom, langue, bouton d'accueil — se retrouvait sous l'heure. */
  /* PAS de zone sure en bas ICI : le pied de page suit toujours .shell et la
     porte deja — la reserver aux deux etages faisait un vide de ~100 px sous
     les liens, visible uniquement dans l application installee (le navigateur
     n a pas d encoche basse). Constate sur captures le 2026-08-16. */
  padding:calc(32px + env(safe-area-inset-top)) calc(20px + env(safe-area-inset-right))
          24px calc(20px + env(safe-area-inset-left));
  flex:1;
  display:flex; flex-direction:column; justify-content:flex-start;
}
/* Centrage vertical réservé aux pages courtes (connexion, installation). */
.shell.center{ justify-content:center; }
.shell.wide{ max-width:820px; }

/* ---------------- la connexion ----------------
   Elle vit dans une FENÊTRE, ouverte par-dessus la page qu'on lisait, et le
   bouton qui l'ouvre est sur toutes les pages publiques. La page login.php
   garde le même formulaire pour qui y arrive autrement : sans JavaScript, sur
   une session expirée, ou par un signet. Voir lib/connexion.php.

   La carte et le champ ne sont pas redéfinis ici : ce sont ceux du site,
   .card et .field, pour que la fenêtre et la page soient identiques à l'œil.
   Une seconde définition aurait dérivé de l'autre à la première retouche. */
.cnx-seule{ width:100%; max-width:400px; margin:0 auto; }

.cnx{
  border:0; padding:0; background:transparent; color:inherit;
  width:min(400px, calc(100vw - 28px)); max-width:none;
  max-height:calc(100dvh - 24px); overflow:auto; overscroll-behavior:contain;
}
.cnx::backdrop{ background:rgba(4, 7, 12, 0.72); backdrop-filter:blur(3px); }
/* La croix se pose SUR le coin de la carte, sans lui prendre de place : posée
   au-dessus, elle décalait le titre et la fenêtre n'était plus celle de la
   page. */
.cnx-fermer{ position:relative; height:0; text-align:right; z-index:2; }
.cnx-fermer button{
  position:relative; top:9px; right:9px;
  width:32px; height:32px; padding:0; border-radius:9px; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center;
  background:transparent; border:1px solid transparent; color:var(--dim);
}
.cnx-fermer button:hover{ background:var(--surface-2); border-color:var(--line); color:var(--text); }
.cnx-fermer button svg{ width:15px; height:15px; }
.cnx .card{ margin:0; }
/* Le bouton d'ouverture : celui de la barre, sans surprise. */
.site-actions{ margin:0; align-items:center; }

.invite-note{ font-size:11.5px; color:var(--dim); text-align:center; margin:13px 6px 0; line-height:1.55; }

/* ---------------- l'accueil public et les tarifs ----------------
   Ces pages réemploient le vocabulaire de « pourquoi » — .why-card, .why-hero,
   .why-checks, .why-sec — plutôt que d'ouvrir une feuille à elles. Seul ce qui
   leur est PROPRE vit ici : trois règles, pas une de plus. */

/* ---------------- la bannière du site ----------------
   ELLE SUIT LE DÉFILEMENT, SUR LES NEUF PAGES PUBLIQUES. Elle n'était que sur
   l'accueil ; ailleurs la marque et les boutons vivaient DANS la colonne de
   texte et disparaissaient au premier défilement, ce qui obligeait à remonter
   en haut de page pour changer de langue ou se connecter.

   Le fond est opaque et flouté : translucide seul, le texte des cartes passait
   au travers. */
.site-bandeau{
  position:sticky; top:0; z-index:40;
  background:color-mix(in srgb, var(--bg) 88%, transparent);
  border-bottom:1px solid var(--line);
  padding-top:env(safe-area-inset-top);
}
@supports (backdrop-filter: blur(10px)){
  .site-bandeau{ backdrop-filter:blur(10px); }
}
.site-bandeau-in{
  max-width:820px; margin:0 auto; padding:9px 18px;
  display:flex; align-items:center; gap:10px;
}
.site-marque{
  display:flex; align-items:center; gap:9px;
  text-decoration:none; color:inherit; min-width:0;
}
.site-marque .mark{ width:30px; height:30px; flex:none; }
.site-marque .wordmark{ font-size:17px; white-space:nowrap; }
.site-bandeau-in .site-actions{ margin-left:auto; gap:7px; }
/* UNE SEULE LIGNE, C'EST LA RÈGLE. « nowrap » n'est pas un ornement : sans
   lui, le bouton qui ne tient plus passe à l'étage du dessous et la bannière
   double de hauteur — c'est ce qui arrivait au téléphone avec deux pastilles
   de langue, « Se connecter » et « Menu » côte à côte. */
.site-bandeau-in .site-actions{ flex-wrap:nowrap; }
/* Les trois boutons ont la MÊME hauteur. « Se connecter » est un .btn.small,
   qui vaut 37 px, tandis que la langue et le menu montent à 44 pour rester
   visables au pouce : à côté d'eux il paraissait tassé. */
.site-actions .btn.small{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px;
}
@media (max-width:479px){
  .site-bandeau-in{ padding:8px 12px; gap:8px; }
  .site-bandeau-in .site-actions{ gap:6px; }
  .site-actions .btn.small{ padding:7px 10px; font-size:11.5px; }
}
/* Sous 400 px, le mot de la marque s'efface et le pictogramme reste seul : il
   la fait reconnaître, il mène lui aussi à l'accueil, et il rend au reste les
   soixante-douze pixels qui manquaient pour tenir la ligne. Le seuil est
   mesuré, pas choisi : à 400 px la barre complète occupe 376 px. */
@media (max-width:399px){
  .site-marque .wordmark{
    position:absolute; width:1px; height:1px; overflow:hidden;
    clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
  }
}
/* La bannière porte déjà la marque : la page ne la répète pas. */
.ac-shell{ padding-top:20px; }
/* Les pages qui avaient une barre dans leur colonne respirent maintenant sous
   la bannière, pas contre elle. */
.shell.wide > .page-head:first-child,
.shell.contact-shell > :first-child{ margin-top:22px; }

/* Le titre de la vitrine reprend le dégradé du hero de connexion : c'est la
   même promesse, elle doit avoir la même voix. */
.ac-hero h1 .hl{
  background:linear-gradient(120deg, var(--acc), var(--acc-2));
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.ac-cta{ margin-top:18px; }
.ac-souscta{ margin-top:14px; }

/* Le numéro d'étape, posé dans le coin de la carte. En filigrane : il ordonne
   la lecture sans se disputer la place avec le titre. */
.ac-etape{ position:relative; }
.ac-etape .ac-num{
  position:absolute; top:10px; right:14px;
  font-family:'Poppins',sans-serif; font-weight:800; font-size:30px; line-height:1;
  color:var(--acc); opacity:0.22; pointer-events:none;
}

/* La carte du prix et celle du chantier : même cadre que .why-hero, teinte
   accentuée pour qu'elles se détachent de ce qui les entoure. */
/* .tf-chantier a disparu avec le panneau « page en construction » des tarifs,
   remplacé par les trois paliers. Le sélecteur ne le cite plus : une règle qui
   ne s'applique à rien finit par être recopiée ailleurs comme un modèle. */
.ac-prix{
  border-color:color-mix(in srgb, var(--acc) 34%, var(--line));
  background:linear-gradient(160deg, #16222c, var(--surface));
}
/* « .card.ac-prix » et non « .ac-prix » : à spécificité égale, c'est l'ordre
   d'écriture qui tranche, et « .card h2 » est déclaré plus bas dans la feuille.
   La règle perdait donc en silence, et le titre restait cyan. Un sélecteur plus
   précis gagne où qu'il soit écrit. */
.card.ac-prix > h2{
  font-size:18px; margin:13px 0 9px; color:var(--text);
}
.card.ac-prix > p{
  font-size:14px; color:var(--dim); line-height:1.68; margin:0 0 14px;
}

/* LES PHOTOGRAPHIES.
   Détourées de leur fond de studio, posées DERRIÈRE le contenu, et fondues
   dans la page par un masque en dégradé. C'est le masque qui les rend
   discrètes, pas un appauvrissement de l'image : une première version les
   réduisait à des silhouettes teintées en cyan, élégantes et illisibles, où
   l'on ne reconnaissait plus un athlète.

   Le masque plutôt qu'un voile posé par-dessus : un voile éclaircit le fond
   noir en même temps que la photo, et laisse une plaque grise visible. Le
   masque, lui, efface vraiment les pixels. */
.ac-photo{
  display:block; height:auto; pointer-events:none; user-select:none;
  /* À peine désaturée : la photo cesse de tirer l'œil avant le titre, sans
     s'effacer. Une première version l'assombrissait d'un cinquième en plus du
     fondu, et l'athlète du hero devenait invisible. */
  filter:saturate(0.86) brightness(0.96) contrast(1.04);
}

/* ---- la scène du hero ----
   Le texte devant, la photo derrière et à droite. Le dégradé du masque part de
   la gauche, là où se lit le titre : la photo y est totalement effacée, et
   n'apparaît qu'une fois le texte fini. */
/* « overflow:hidden » : la photo est posée en absolu et déborde volontairement
   du cadre pour que la barre sorte du champ. Sans cette ligne, ce débordement
   élargit la page entière et fait apparaître une barre de défilement
   horizontale sur téléphone. Le hero coupe donc ce qui sort de lui. */
.ac-hero{ position:relative; overflow:hidden; }
/* LA PLACE EST CALCULÉE, PAS ESTIMÉE. Dans l'image, l'athlète occupe le tiers
   central : sa tête va de 35,5 % à 48,4 % de la largeur du cadre, le reste est
   la barre et les disques. Photo décalée de 6 % à droite et texte arrêté à
   56 %, sa tête commence à 66 % du hero : dix points la séparent de la
   dernière lettre. Elle passait avant à 60 %, exactement là où le texte
   finissait, et les lignes venaient lui toucher les cheveux.

   Ce qui sort à droite est l'embout de la barre, que le hero coupe : une barre
   qui continue hors du cadre se lit comme un cadrage, pas comme un défaut. */
.ac-photo-squat{
  position:absolute; right:-6%; bottom:0;
  width:min(62%, 620px); z-index:0;
  opacity:0.82;
  -webkit-mask-image:linear-gradient(100deg, transparent 5%, rgba(0,0,0,.35) 22%, #000 48%),
                     linear-gradient(to top, transparent 0%, #000 16%);
          mask-image:linear-gradient(100deg, transparent 5%, rgba(0,0,0,.35) 22%, #000 48%),
                     linear-gradient(to top, transparent 0%, #000 16%);
  -webkit-mask-composite:source-in;
          mask-composite:intersect;
}
/* Le texte passe devant, toujours. */
.ac-hero-txt{ position:relative; z-index:1; }

/* Sous 720 px la photo passe DERRIÈRE le texte, très effacée : elle donne le
   ton sans disputer la place au titre ni au bouton. Elle ne disparaît plus,
   comme c'était le cas avec les silhouettes : on ne voyait alors qu'une seule
   image sur toute la page. */
@media (max-width:719px){
  .ac-photo-squat{
    right:-14%; bottom:auto; top:-8px;
    width:78%; opacity:0.42;
    -webkit-mask-image:linear-gradient(180deg, #000 0%, transparent 82%);
            mask-image:linear-gradient(180deg, #000 0%, transparent 82%);
  }
}

/* ---- la scène du coureur ----
   Même traitement que le hero : la photo derrière, le contenu devant, et le
   fondu qui la fait entrer dans la page. Elle vivait avant dans une bande à
   filets, entre deux sections : un cadre au milieu de rien, où l'image se
   voyait pour ce qu'elle était, collée là.

   La hauteur est celle de la section : la photo se cale sur le titre et la
   carte, elle n'impose pas sa propre place. */
.ac-scene{ position:relative; overflow:hidden; margin-top:26px; }
/* Le titre passe devant la photo, mais SANS que son bloc devienne positionné :
   « position:relative » sur .ac-scene-haut en aurait fait le repère de la
   photo, qui se serait calée sur la hauteur du titre au lieu de celle de la
   section. C'est donc le titre lui-même qui se relève. */
.ac-scene-txt{ position:relative; z-index:1; }
.ac-scene-haut > .why-sec{ position:relative; z-index:1; }
/* LE FONDU NE DOIT PAS AMPUTER L'ATHLÈTE. Une première version effaçait la
   moitié gauche de l'image et son cinquième inférieur : la jambe arrière du
   coureur disparaissait, ses deux chaussures aussi, et il restait un tronc en
   suspension. Le dégradé de gauche s'arrête donc là où la carte s'arrête, et
   celui du bas ne mange que la semelle.

   « object-fit » et une largeur plafonnée, plutôt que la seule hauteur : la
   carte est opaque et passe DEVANT la photo. Réglée sur la seule hauteur de la
   section, la photo s'élargissait avec elle et finissait sous la carte, dont
   le bord droit lui tranchait la jambe arrière. Elle a maintenant sa colonne,
   et la carte s'arrête avant. */
.ac-photo-coureur{
  position:absolute; right:0; bottom:0; z-index:0;
  height:100%; width:auto; max-width:40%;
  object-fit:contain; object-position:right bottom;
  opacity:0.62;
  -webkit-mask-image:linear-gradient(to right, transparent 0%, rgba(0,0,0,.45) 12%, #000 30%),
                     linear-gradient(to top, transparent 0%, #000 12%);
          mask-image:linear-gradient(to right, transparent 0%, rgba(0,0,0,.45) 12%, #000 30%),
                     linear-gradient(to top, transparent 0%, #000 12%);
  -webkit-mask-composite:source-in;
          mask-composite:intersect;
}
/* Sous 820 px la carte prend toute la largeur, et la photo n'a plus de place à
   côté d'elle. Deux essais l'ont montré : une bande réservée au-dessus de la
   section la laissait seule à droite dans un grand vide, et une ligne avec le
   titre lui demandait encore de la place que la page n'a pas.

   Elle ne prend donc PLUS AUCUNE PLACE : elle passe derrière, la carte devient
   translucide et le coureur se voit au travers. Le flou du fond garde le texte
   net : ce qui transparaît est une matière, pas une image à déchiffrer sous
   les mots. */
@media (max-width:819px){
  /* Calé EN HAUT de la section : sa tête et ses épaules paraissent en clair, à
     côté du titre, et le reste du corps s'efface derrière la carte. Calé en
     bas, il disparaissait tout entier sous elle et il ne restait qu'un
     fantôme, qu'on ne reconnaissait plus. */
  .ac-photo-coureur{
    right:0; top:0; bottom:auto;
    height:auto; width:70%; max-width:none;
    opacity:0.72;
    -webkit-mask-image:linear-gradient(to right, transparent 0%, rgba(0,0,0,.45) 16%, #000 40%),
                       linear-gradient(to top, transparent 8%, #000 42%);
            mask-image:linear-gradient(to right, transparent 0%, rgba(0,0,0,.45) 16%, #000 40%),
                       linear-gradient(to top, transparent 8%, #000 42%);
  }
  .ac-scene .card{
    background:color-mix(in srgb, var(--surface) 74%, transparent);
  }
  @supports (backdrop-filter: blur(3px)){
    .ac-scene .card{ backdrop-filter:blur(3px); }
  }
}
/* Au-delà, le texte laisse la colonne de droite à la photo. 58 % et 40 % :
   les deux ne se recouvrent jamais, quelle que soit la hauteur de la carte.
   Le titre suit la même limite que la carte, sinon il courrait sous la photo. */
@media (min-width:820px){
  .ac-scene-txt, .ac-scene-haut{ max-width:58%; }
}
/* Le texte n'occupe que la moitié gauche dès que la photo a la place de
   paraître : sans cette limite, les lignes passeraient sous la barre. */
@media (min-width:720px){
  .ac-hero-txt{ max-width:56%; }
}

/* Le lede des tarifs suit la largeur de la page, et non la mesure de lecture
   des autres pages : c'est une phrase, pas un paragraphe, et coupée à soixante
   caractères elle laissait un vide à droite qu'on lisait comme une erreur. */
.page-head .lede.tf-lede{ max-width:none; }
/* Une croix n'est pas une coche : elle dit « il n'y en aura pas », pas
   « c'est raté ». Le gris du texte secondaire, jamais le rouge d'une erreur. */
.tf-sans .ico{ color:var(--dim); }

/* ---------------- les trois paliers ----------------
   UNE COLONNE, PUIS TROIS — jamais deux.
   Une grille « auto-fit » aurait paru plus souple, et c'est ce que j'avais
   écrit : mesurée à 728 px, elle rangeait deux paliers sur la première ligne et
   le troisième tout seul en dessous. Une grille de tarifs déséquilibrée se lit
   comme une offre déséquilibrée — le palier isolé passe pour une exception.
   Avec trois éléments et trois seulement, la bascule se décide, elle ne se
   calcule pas. Le seuil de 620 px est celui où les trois cartes tiennent sans
   que la phrase de chacune parte en colonne de mots.
   Le montant est le seul élément en couleur d'accent : c'est ce que l'œil
   cherche, et lui donner de la concurrence ferait perdre du temps à tout le
   monde. */
.tf-paliers{
  display:grid; gap:12px; margin:0 0 18px;
  grid-template-columns:1fr;
}
@media (min-width:620px){
  .tf-paliers{ grid-template-columns:repeat(3, 1fr); }
}
/* MARGE REMISE À ZÉRO, et ce n'est pas de la cosmétique.
   « .card + .card » pose 14 px entre deux cartes qui se suivent — ce qui est
   juste quand elles s'empilent, et faux dans une grille, où le « gap » fait
   déjà l'espacement. Sur un élément de grille étiré, c'est la boîte AVEC ses
   marges qui remplit la rangée : les deuxième et troisième cartes perdaient
   14 px de hauteur et descendaient d'autant. Mesuré à 1280 px avant
   correction — hauteurs 207 / 193 / 193, sommets 207 / 221 / 221. Trois tarifs
   mal alignés se lisent comme une offre mal ficelée.
   LE SÉLECTEUR PORTE TROIS CLASSES, et c'est le sujet : « .card.tf-palier »
   pèse autant que « .card + .card », et à poids égal c'est l'ordre d'écriture
   qui tranche — celui-là est déclaré plus bas, il gagnait. Essayé, mesuré sans
   effet, puis corrigé. La feuille documente déjà le même piège pour
   « .card.ac-prix », quelques centaines de lignes plus haut. */
.tf-paliers .card.tf-palier{ display:flex; flex-direction:column; margin:0; }
/* LA DESCRIPTION RESTE ANCRÉE EN HAUT, contre l'entête, et non poussée en bas
   de la carte. Essayé dans l'autre sens : les phrases n'ont pas le même nombre
   de lignes, et le filet de séparation se retrouvait à trois hauteurs
   différentes. Au-dessus, les trois éléments (nom, montant, durée) portent un
   texte identique d'une carte à l'autre, donc ils occupent exactement la même
   hauteur : le filet forme une ligne continue sans qu'on ait à la calculer. */
.tf-palier-nom{
  font-size:12px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:var(--dim);
}
/* LE MONTANT EN DEUX TEMPS. « CHF » en petit et discret, le nombre en grand et
   en couleur d'accent, les deux posés sur la MÊME LIGNE DE BASE. Écrits à la
   même taille, les trois lettres de la devise pesaient autant que le chiffre
   alors qu'elles sont identiques sur les trois cartes : l'œil les relisait
   trois fois pour rien. Ici il ne lit que ce qui change. */
.tf-palier-prix{
  display:flex; align-items:baseline; gap:6px;
  margin:8px 0 2px; line-height:1;
}
.tf-devise{ font-size:13px; font-weight:700; color:var(--dim); letter-spacing:.04em; }
/* tabular-nums : 29, 49 et 69 occupent alors exactement la même largeur, et les
   trois montants s'alignent au pixel d'une carte à l'autre. */
.tf-nombre{
  font-size:40px; font-weight:800; color:var(--acc); line-height:1;
  font-variant-numeric:tabular-nums;
}
.tf-palier-unite{ font-size:12.5px; color:var(--dim); line-height:1.5; }
/* Le filet sépare le prix de ce qu'il achète. Il est posé sur le paragraphe
   plutôt qu'en élément à part : un trait qui n'existe que par le HTML finit
   par manquer là où on l'a oublié. */
.tf-palier-pour{
  font-size:13.5px; line-height:1.6; color:var(--text);
  margin:14px 0 0; padding-top:14px; border-top:1px solid var(--line);
}

/* L'option n'est pas un quatrième palier : elle a sa propre carte, pleine
   largeur, avec le nom et le supplément aux deux bouts de la même ligne. */
.tf-option{ margin:0 0 18px; }
.tf-option-tete{
  display:flex; align-items:baseline; justify-content:space-between;
  gap:12px; flex-wrap:wrap; margin-bottom:10px;
}
.card.tf-option > p{ font-size:14px; color:var(--dim); line-height:1.68; margin:0; }
/* « par 4 semaines », collé au montant et posé sur la même ligne de base que
   lui. Sans cette mention, « + CHF 39 » se lit comme le prix du cycle entier. */
.tf-par{ font-size:12.5px; font-weight:600; color:var(--dim); }
/* Les quatre durées et leur total. Une grille plutôt qu'une liste à puces :
   les montants doivent s'aligner entre eux pour se comparer d'un coup d'oeil,
   et tabular-nums garde 39 et 156 sur la même largeur. */
.tf-option-durees{
  list-style:none; margin:14px 0 0; padding:14px 0 0;
  border-top:1px solid var(--line);
  display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:8px 18px;
}
@media (min-width:620px){
  .tf-option-durees{ grid-template-columns:repeat(4, minmax(0, 1fr)); }
}
.tf-option-durees li{
  display:flex; align-items:baseline; justify-content:space-between; gap:8px;
  font-size:13.5px; font-variant-numeric:tabular-nums;
}
.tf-option-duree{ color:var(--dim); }
.tf-option-durees strong{ color:var(--text); font-weight:700; }

/* La phrase qui dit comment le palier est choisi, sous la grille des trois
   prix. Elle a remplacé le panneau des prix de lancement, retiré le
   21.08.2026 : même place dans la page, beaucoup moins de décor. */
.tf-note{
  margin:-4px 0 22px;
  font-size:14px;
  color:var(--dim);
  line-height:1.68;
  text-align:center;
}


/* ---------------- décor sportif (en sommeil) ----------------
   Les silhouettes dessinées ont été retirées des pages en attendant des
   photographies sous licence. Ces règles restent : elles décrivent une mise en
   page — un bandeau sous un texte, une image à côté d'un titre — qui servira
   telle quelle aux photographies. Seule la source de l'image changera.

   Les silhouettes vivent DANS le flux. Un premier essai les plaçait en
   filigrane derrière le contenu : les cartes, opaques, les mangeaient, et la
   marge libre nécessaire n'existe qu'au-delà de 1100 px de large. */
/* Une silhouette PLEINE pèse beaucoup plus lourd à l'œil qu'un trait : d'où
   une opacité franchement plus basse que pour les pictogrammes du site. */
.sport-art{ color:var(--acc); height:auto; flex:none; display:block; }

/* Bandeau : les figures se posent sous un texte, alignées sur le sol. */
.sport-band{
  display:flex; align-items:flex-end; gap:18px;
  margin:20px 0 0; opacity:.26;
}
.sport-band .sport-art{ width:clamp(74px, 12vw, 108px); }

/* À côté d'un titre : une seule figure, poussée vers la droite. */
.sport-aside{ display:flex; align-items:flex-end; opacity:.24; }
.sport-aside .sport-art{ width:clamp(80px, 13vw, 120px); }

/* Titre de page accompagné d'une figure : le texte garde toute sa place, la
   figure ne prend que ce qui reste. */
.page-head.with-art, .ct-head.with-art{
  display:flex; align-items:flex-end; justify-content:space-between; gap:24px;
}
.page-head.with-art > div:first-child,
.ct-head.with-art > div:first-child{ flex:1 1 auto; min-width:0; }

@media (max-width:560px){
  /* Sur téléphone la hauteur est précieuse : la figure d'appoint disparaît
     plutôt que de repousser le formulaire vers le bas. */
  .sport-aside{ display:none; }
  .sport-band{ gap:14px; margin-top:16px; }
}

/* ---------------- pourquoi ProgLabAi ---------------- */
.why-ico{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:38px; height:38px; border-radius:11px;
  background:var(--surface-2); border:1px solid var(--line); color:var(--acc);
}
.why-ico svg{ width:19px; height:19px; }
.why-ico.small{ width:30px; height:30px; border-radius:9px; }
.why-ico.small svg{ width:16px; height:16px; }
.why-ico.big{ width:46px; height:46px; border-radius:13px; }
.why-ico.big svg{ width:24px; height:24px; }

.why-grid{ display:grid; gap:14px; margin-bottom:16px; }
.why-card{
  background:var(--surface); border:1px solid var(--line); border-radius:16px;
  padding:20px; box-shadow:0 18px 40px -28px #000;
}
.why-card h2{ font-size:15.5px; margin:13px 0 7px; font-weight:700; color:var(--text); }
.why-card p{ font-size:13.5px; color:var(--dim); line-height:1.68; margin:0; }

/* La relecture par un coach : c'est l'argument central, il est traité comme
   tel — encadré à l'accent, et non noyé dans la grille. */
.why-hero{ border-color:#2f4a5c; background:linear-gradient(160deg, #16222c, var(--surface)); }
.why-hero h2{ font-size:18px; margin:13px 0 9px; color:var(--text); }
.why-hero > p{ font-size:14px; color:var(--dim); line-height:1.68; margin:0 0 14px; }
.why-strong{ font-size:13.5px; color:var(--text) !important; margin:14px 0 0 !important; font-weight:600; }

.why-checks{ list-style:none; margin:0; padding:0; display:grid; gap:10px; }
.why-checks li{
  display:flex; align-items:flex-start; gap:10px;
  font-size:13.5px; color:var(--dim); line-height:1.6;
}
.why-checks li .ico{ flex:none; color:var(--success); margin-top:1px; }
.why-checks li .ico svg{ width:17px; height:17px; }
/* Le texte dans son propre bloc : sans lui, chaque <strong> deviendrait un
   élément flex à part et la phrase se répartirait en colonnes. */
.why-checks li .t{ min-width:0; }
.why-checks.big li{ font-size:14px; }
.why-checks strong{ color:var(--text); font-weight:600; }

.why-sec{
  display:flex; align-items:center; gap:11px;
  font-size:17px; font-weight:700; margin:26px 0 13px;
}
.why-qa{ display:grid; gap:12px; }
/* La barre d'accent à gauche sépare l'objection de sa réponse d'un coup d'œil. */
.why-q{
  background:var(--surface); border:1px solid var(--line); border-left:3px solid var(--acc);
  border-radius:0 14px 14px 0; padding:16px 18px;
}
.why-q .q{ font-size:14.5px; font-weight:700; color:var(--text); margin:0 0 8px; }
.why-q .r{ font-size:13.5px; color:var(--dim); line-height:1.68; margin:0; }

.why-note{ display:flex; gap:14px; align-items:flex-start; margin-top:16px; }
.why-note h2{ font-size:15px; margin:0 0 8px; color:var(--text); }
.why-note p{ font-size:13px; color:var(--dim); line-height:1.65; margin:0 0 8px; }
.why-note p:last-child{ margin-bottom:0; }

.why-cta{ margin-top:16px; text-align:center; }
.why-cta h2{ font-size:18px; margin:0 0 6px; color:var(--acc); }
.why-cta .btn-row{ justify-content:center; margin-top:14px; }
.why-cta .btn-row .btn{ width:auto; }

@media (min-width:760px){
  .why-grid{ grid-template-columns:repeat(3, 1fr); }
  .why-hero{ padding:26px 24px; }
}
@media (max-width:520px){
  .why-note{ flex-direction:column; gap:10px; }
  .why-cta .btn-row .btn{ width:100%; }
}

/* ---------------- présentation des coachs ---------------- */
.page-head{ margin:0 0 18px; }
.page-head h1{ font-size:26px; letter-spacing:-0.6px; margin:0 0 8px; font-weight:800; }
/* L accueil porte AUSSI « page-head » : sa regle doit venir apres celle-ci,
   sinon les deux se valent en specificite et c est la derniere ecrite qui
   gagne. Le titre de la vitrine se retrouvait a la taille d une page
   interieure. */
.page-head.ac-hero h1{ font-size:29px; line-height:1.14; letter-spacing:-0.8px; margin:0 0 13px; }
/* Ecrite JUSTE APRES la regle de base, et non dans le bloc de l'accueil plus
   haut : a specificite egale c'est l'ordre qui tranche, et la version large
   du titre etait annulee par la version etroite declaree apres elle. */
@media (min-width:720px){
  .page-head.ac-hero h1{ font-size:41px; letter-spacing:-1.2px; }
}
.page-head .lede{ font-size:14px; color:var(--dim); margin:0; line-height:1.6; max-width:60ch; }

.coach-list{ display:grid; gap:14px; }
.coach{
  display:flex; gap:16px; align-items:flex-start;
  background:var(--surface); border:1px solid var(--line); border-radius:16px;
  padding:20px; box-shadow:0 18px 40px -28px #000;
}
/* UN VRAI PORTRAIT, PAS UNE VIGNETTE. La page des coachs est celle qui
   inspire confiance : on veut voir la personne en entier, dans sa salle, et
   non un timbre de 76 px où le visage tient en six pixels. D'où le format
   3:4 — celui d'une photo de personne debout — et une largeur qui laisse
   encore de quoi lire à côté. */
.coach-photo{
  width:220px; aspect-ratio:3 / 4; height:auto; flex:none;
  border-radius:14px; object-fit:cover;
  border:1px solid var(--line); background:var(--surface-2);
}
/* Sans photo, les initiales : un cadre vide ferait plus pauvre que pas de photo. */
.coach-photo.initials{
  display:flex; align-items:center; justify-content:center;
  font-size:52px; font-weight:800; letter-spacing:-1px;
  color:var(--acc);
  background:linear-gradient(140deg, var(--surface-2), #1a2436);
}
.coach-body{ min-width:0; }
.coach-body h2{ font-size:17px; margin:2px 0 3px; font-weight:700; color:var(--text); }
.coach-role{ font-size:12.5px; color:var(--acc); margin:0 0 10px; font-weight:600; }
.coach-text{ font-size:13.5px; color:var(--dim); line-height:1.65; margin:0; }
/* Une présentation se lit en plusieurs temps : le parcours, puis l'histoire.
   L'écart ne vit qu'ENTRE deux paragraphes, jamais après le dernier. */
.coach-text + .coach-text{ margin-top:11px; }
.coach-tags{ list-style:none; display:flex; flex-wrap:wrap; gap:6px; margin:12px 0 0; padding:0; }
.coach-tags li{
  font-size:11px; padding:4px 9px; border-radius:999px;
  background:var(--surface-2); border:1px solid var(--line); color:var(--dim);
}
/* auto-fit plutôt que deux colonnes fixes : un coach seul occupe toute la
   largeur au lieu de laisser un demi-écran vide à côté de lui. */
/* PLUS DE DEUX COLONNES : depuis que la photo est un portrait de 220 px, un
   demi-écran ne laisserait qu'une centaine de pixels au texte. Une carte par
   ligne, sur toute la largeur — c'est aussi ce qui se lit le mieux quand il
   n'y a qu'un coach à présenter. */
@media (min-width:720px){
  .page-head h1{ font-size:30px; }
}
@media (max-width:560px){
  .coach{ flex-direction:column; gap:14px; }
  /* La photo garde ses proportions et sa taille : au-delà, elle mangerait
     tout l'écran avant qu'on ait lu une ligne. */
  .coach-photo{ width:100%; max-width:240px; }
}

/* Clé TOTP à recopier dans l'application d'authentification. */
.totp-secret{
  font-family:ui-monospace,Menlo,Consolas,monospace; font-size:15px; letter-spacing:1px;
  background:var(--surface-2); border:1px solid var(--line); border-radius:10px;
  padding:12px 14px; margin:0 0 12px; word-break:break-all; user-select:all;
}

/* Mot de passe oublié : discret sous le bouton de connexion. */
.fp-link{ display:block; text-align:center; margin-top:12px; font-size:12px; color:var(--dim); text-decoration:none; }
.fp-link:hover{ color:var(--acc); text-decoration:underline; }

/* Seule porte d'entrée pour qui n'a pas encore de compte : elle doit se voir
   sans venir concurrencer le bouton de connexion. */
.ask-cta{
  display:flex; align-items:center; justify-content:center; gap:8px;
  margin:12px 0 0; padding:12px 14px;
  border:1px solid var(--line); border-radius:12px; background:var(--surface);
  color:var(--text); text-decoration:none; font-size:13px; font-weight:600;
  transition:border-color .15s, transform .12s;
}
.ask-cta:hover{ border-color:var(--acc); }
.ask-cta:active{ transform:scale(.99); }
.ask-cta svg{ width:16px; height:16px; flex:none; color:var(--acc); }

/* ---------------- prose justifiée, sur la vitrine seulement ----------------
   Un bord droit net donne à une page l'air d'être composée plutôt que tapée.
   Trois précautions, sans quoi il fait l'inverse.

   1. SEULEMENT AU-DESSUS DE 720 px. Justifier une colonne de quarante-cinq
      caractères, ce qu'est un téléphone, ouvre entre les mots des trous que
      l'œil suit verticalement — les « rivières » des typographes. En dessous,
      le texte reste au fer à gauche.
   2. AVEC LA COUPURE DES MOTS. C'est elle qui absorbe l'espace au lieu de le
      reporter entre les mots ; « hyphens » travaille d'après la langue, que
      <html lang> déclare déjà.
   3. SEULEMENT LES BLOCS LARGES. Mesuré sur la page : les paragraphes pleine
      largeur font 76 à 106 caractères par ligne, les réponses aux objections
      91 à 106, et la justification y est invisible autrement que par le bord
      droit net. Les cartes en trois colonnes, elles, font 209 px, soit 26 à
      31 caractères : justifiées, elles seraient trouées. Elles restent donc au
      fer à gauche, comme les listes à coches, dont la plupart des entrées
      tiennent de toute façon sur une ligne.
   4. LES BLOCS CENTRÉS GARDENT LEUR ALIGNEMENT — l'appel à l'action, la note
      d'invitation, l'écran de confirmation. Et rien n'est justifié dans
      l'application, faite de listes et de chiffres. */
@media (min-width:720px){
  body.site-public .card > p,
  body.site-public .why-q .r,
  body.site-public .legal p,
  body.site-public .legal li,
  body.site-public .page-head .lede{
    text-align:justify;
    -webkit-hyphens:auto; hyphens:auto;
    /* Un mot seul sur la dernière ligne d'un paragraphe justifié est ce qui se
       voit le plus : la ligne d'avant est étirée pour rien. */
    text-wrap:pretty;
  }
  /* Ce qui est centré le reste : la justification n'a rien à y faire. */
  body.site-public .why-cta p,
  body.site-public .ct-done p,
  body.site-public .invite-note{ text-align:center; }
}

/* ---------------- marque ---------------- */
/* La marque est un LIEN vers l'accueil, sur toutes les pages : elle en garde
   la couleur du texte et n'en prend pas le soulignement. */
.brand{
  display:flex; flex-direction:column; align-items:center; gap:14px; margin-bottom:26px;
  color:inherit; text-decoration:none;
}
.brand .mark{ width:76px; height:76px; }
.brand.small{ flex-direction:row; justify-content:flex-start; gap:12px; margin-bottom:22px; }
.brand.small .mark{ width:40px; height:40px; }
.wordmark{
  font-size:26px; font-weight:800; letter-spacing:-0.5px; line-height:1;
  display:flex; align-items:baseline; gap:1px;
}
.brand.small .wordmark{ font-size:19px; }
.wordmark .ai{ color:var(--acc); }
.brand .tagline{ font-size:12px; color:var(--dim); letter-spacing:1.5px; text-transform:uppercase; }
.brand.small .tagline{ display:none; }

/* ---------------- cartes / formulaires ---------------- */
.card{
  background:var(--surface); border:1px solid var(--line); border-radius:16px;
  padding:22px 20px; box-shadow:0 18px 40px -28px #000;
}
.card + .card{ margin-top:14px; }
.card h1{ font-size:18px; margin:0 0 4px; font-weight:700; }
.card h2{ font-size:15px; margin:0 0 12px; font-weight:700; color:var(--acc); }
.card .sub{ font-size:12.5px; color:var(--dim); margin:0 0 18px; line-height:1.5; }

.field{ margin-bottom:14px; }
.field label{ display:block; font-size:11px; text-transform:uppercase; letter-spacing:0.6px; color:var(--dim); margin-bottom:6px; }
/* Tous les champs saisissables, y compris number et date, sur toute la largeur. */
.field input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.field select{
  width:100%; background:var(--surface-2); border:1px solid var(--line); color:var(--text);
  padding:12px 13px; border-radius:10px; font-size:16px; font-family:inherit;
}
/* Même correction que dans assets/program.css, et pour la même raison : sur
   iOS, un « input[type=date] » porte une largeur intrinsèque que width:100%
   ne réduit pas, et il dépasse de sa carte par la droite. Voir le commentaire
   détaillé là-bas. Ici cela vaut pour la demande de programmation, la fiche
   d'un athlète et le cahier des charges — tous trois portent un champ de date. */
.field input[type="date"]{
  appearance:none; -webkit-appearance:none;
  min-width:0; max-width:100%; display:block;
}
.field input[type="date"]::-webkit-date-and-time-value{ text-align:left; margin:0; }
.field input:focus, .field select:focus{ outline:none; border-color:var(--acc); }
/* Les cases à cocher ne reprennent pas les majuscules des étiquettes de champ. */
.field .check{ text-transform:none; letter-spacing:0; color:var(--text); font-size:13px; margin-bottom:0; }
.field .hint{ font-size:11px; color:var(--dim); margin-top:5px; }
/* « hint » n'était stylée QUE dans un champ de formulaire. Employée ailleurs —
   sous un interrupteur de notification, par exemple — elle retombait à la
   taille du corps de texte et pesait plus lourd que ce qu'elle commente.
   La règle générale la rattrape ; celle du dessus reste, plus précise. */
.hint{ font-size:11.5px; line-height:1.55; color:var(--dim); }
.field input.mismatch{ border-color:var(--danger); }

/* Bouton œil : afficher / masquer un mot de passe */
.pw-wrap{ position:relative; display:block; }
.pw-wrap input{ padding-right:46px; }
.pw-eye{
  position:absolute; top:0; right:0; height:100%;
  display:flex; align-items:center; justify-content:center;
  width:44px; padding:0; border:none; background:none;
  color:var(--dim); cursor:pointer;
}
.pw-eye:hover, .pw-eye:focus-visible{ color:var(--text); outline:none; }
.pw-eye svg{ width:19px; height:19px; }

/* Politique de mot de passe, cochée en direct */
.pw-rules{ list-style:none; margin:8px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:5px 12px; }
.pw-rules li{ font-size:11px; color:var(--dim); display:flex; align-items:center; gap:5px; }
.pw-rules li::before{ content:"○"; font-size:11px; line-height:1; }
.pw-rules li.ok{ color:var(--success); }
.pw-rules li.ok::before{ content:"●"; }
.pw-rules li.ko{ color:var(--danger); }
.pw-rules li.ko::before{ content:"○"; }

.checks{ display:flex; flex-wrap:wrap; gap:8px; }
.check{
  display:flex; align-items:center; gap:7px; cursor:pointer;
  background:var(--surface-2); border:1px solid var(--line); border-radius:10px;
  padding:9px 12px; font-size:13px;
}
.check input{ width:16px; height:16px; accent-color:var(--acc); }
.check.on{ border-color:var(--acc); }
/* Le meme surlignage, mais suivi PAR LA CASE. « .on » est pose par le script
   apres la reponse du serveur : dans un formulaire ordinaire — les alertes par
   e-mail, qui s'enregistrent au bouton — il ne bougerait qu'apres l'envoi, et
   decocher n'aurait l'air de rien faire jusque-la. */
.check:has(input:checked){ border-color:var(--acc); }

.btn{
  width:100%; border:none; border-radius:10px; padding:13px 16px;
  font-size:15px; font-weight:700; font-family:inherit; cursor:pointer;
  background:linear-gradient(135deg, var(--acc), var(--acc-2)); color:var(--sur-acc);
  text-decoration:none; text-align:center; display:inline-block;
}
.btn:active{ transform:translateY(1px); }
/* Un bouton qui travaille. Il reste EN PLACE et garde sa taille — le libellé
   change pour un mot de même longueur, sinon le bouton rétrécit sous le doigt
   au moment précis où l'on cherche à savoir si le clic a porté. Le curseur et
   l'opacité disent « ça part », le pointer-events coupe les clics suivants
   même pendant les quelques millisecondes où le navigateur n'a pas encore
   quitté la page. */
.btn[disabled]{ opacity:.6; cursor:progress; pointer-events:none; }
.btn[disabled]:active{ transform:none; }
.btn.secondary{ background:var(--surface-2); color:var(--text); border:1px solid var(--line); }
.btn.danger{ background:var(--surface-2); color:var(--danger); border:1px solid var(--line); }
/* ─── SUPPRIMER SON COMPTE ────────────────────────────────────────────────
   La carte du bas porte un filet rouge sur son bord gauche : elle ne se
   confond avec aucune autre, et l'oeil sait avant de lire que ce qui est
   dedans ne se defait pas. */
.danger-zone{ border-left:3px solid var(--danger); }
.danger-zone h2{ color:var(--danger); }

/* L'AVERTISSEMENT PREND L'ECRAN. Un <dialog> natif : il pose son propre
   fond, piege le clavier et se ferme par Echap, sans une ligne de
   bibliotheque. Le ::backdrop est ce qui coupe la page derriere. */
.suppr-dialog{
  border:1px solid var(--line); border-radius:16px; padding:0;
  background:var(--surface); color:var(--text);
  width:min(460px, calc(100vw - 32px)); max-height:calc(100vh - 32px);
}
.suppr-dialog::backdrop{ background:rgba(4,6,10,.72); }
.suppr-boite{ padding:22px; display:flex; flex-direction:column; gap:14px; }
.suppr-titre{
  margin:0; font-size:17px; font-weight:800; line-height:1.35;
  color:var(--danger); font-family:'Poppins',sans-serif;
}
.suppr-liste{ margin:0; padding-left:20px; display:flex; flex-direction:column; gap:7px; }
.suppr-liste li{ font-size:13.5px; line-height:1.5; }
/* La derniere phrase n'est pas une puce de plus : c'est la conclusion, et
   elle doit se lire meme par quelqu'un qui saute la liste. */
.suppr-final{
  margin:0; padding:10px 12px; border-radius:10px;
  background:color-mix(in srgb, var(--danger) 12%, transparent);
  color:var(--text); font-size:13.5px; font-weight:600;
}
.suppr-champ{ display:flex; flex-direction:column; gap:6px; font-size:12.5px; color:var(--text-dim); }
.suppr-champ input{
  background:var(--surface-2); color:var(--text);
  border:1px solid var(--line); border-radius:10px;
  padding:11px 12px; font-size:15px; font-family:inherit;
}
.suppr-coche{ display:flex; gap:9px; align-items:flex-start; font-size:13px; line-height:1.45; }
.suppr-coche input{ margin-top:2px; width:18px; height:18px; flex:0 0 auto; }
.suppr-boutons{ display:flex; gap:10px; justify-content:flex-end; flex-wrap:wrap; }
/* 44 px sous le doigt : c'est un geste definitif, il ne doit pas se rater
   ni se toucher par accident. */
.suppr-boutons .btn{ min-height:44px; }

.btn.small{ width:auto; padding:8px 13px; font-size:12.5px; border-radius:8px; }
.btn-row{ display:flex; gap:8px; flex-wrap:wrap; margin-top:6px; }

/* Retour à l'accueil. L'icône seule serait plus compacte, mais une maison
   isolée se confond avec un bouton de réglages : le mot reste. */
.home-btn{ display:inline-flex; align-items:center; gap:6px; position:relative; }
.home-btn svg{ width:15px; height:15px; flex:none; color:var(--acc); }
@media (max-width:400px){
  /* Sur un écran étroit la barre supérieure doit tenir sur une ligne : le mot
     disparaît de l'écran mais PAS de la page. display:none le retirerait aussi
     de l'arbre d'accessibilité, et le lien n'aurait plus de nom du tout. */
  .home-btn span{
    position:absolute; width:1px; height:1px; overflow:hidden;
    clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
  }
  .home-btn{ padding-left:10px; padding-right:10px; }
}

/* Menu des pages du site. Le panneau s'ouvre et se ferme par <details> : sans
   JavaScript il reste utilisable, il ne se referme simplement plus tout seul. */
.site-menu{ position:relative; flex:none; }
/* summary.btn et non summary seul : .btn.small impose display:inline-block et
   gagnerait la cascade à spécificité égale. */
.site-menu > summary.btn{
  display:inline-flex; align-items:center; gap:6px;
  /* 44 px de côté, la cible tactile minimale : le bouton du menu est le voisin
     immédiat du sélecteur de langue, et rater l'un pour l'autre est le geste le
     plus facile de la barre.
     La largeur compte autant que la hauteur : sous 400 px le libellé « Menu »
     est masqué et il ne reste que l'icône — 38 px mesurés, d'où le min-width.
     Il ne bride rien au-delà : avec le libellé, le bouton dépasse déjà 44. */
  min-height:44px; min-width:44px; justify-content:center;
  list-style:none; cursor:pointer; user-select:none;
}
.site-menu > summary::-webkit-details-marker{ display:none; }
.site-menu > summary svg{ width:16px; height:16px; flex:none; color:var(--acc); }
.site-menu[open] > summary.btn{ border-color:var(--acc); }

.site-pop{
  position:absolute; right:0; top:calc(100% + 6px); z-index:40;
  min-width:214px; padding:6px;
  display:flex; flex-direction:column; gap:2px;
  background:var(--surface); border:1px solid var(--line); border-radius:12px;
  box-shadow:0 22px 44px -20px #000;
}
.site-item{
  display:flex; align-items:center; gap:9px;
  /* Les entrées du menu sont empilées : une cible trop courte fait ouvrir la
     page du dessus ou du dessous, et on ne s'en aperçoit qu'après le
     chargement. min-height, pas height : un libellé qui passe à la ligne dans
     une autre langue doit pouvoir grandir. */
  min-height:44px; padding:9px 10px; border-radius:9px;
  color:var(--text); text-decoration:none; font-size:13px; font-weight:600;
}
.site-item svg{ width:16px; height:16px; flex:none; color:var(--dim); }
.site-item:hover{ background:var(--surface-2); }
.site-item.on{ background:var(--surface-2); }
.site-item.on svg{ color:var(--acc); }

@media (max-width:400px){
  /* Même précaution que pour le bouton d'accueil : masqué à l'œil, pas à
     l'arbre d'accessibilité. */
  .site-menu > summary span{
    position:absolute; width:1px; height:1px; overflow:hidden;
    clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
  }
  .site-menu > summary.btn{ padding-left:10px; padding-right:10px; }
}

.alert{
  border-radius:10px; padding:11px 13px; font-size:13px; margin-bottom:16px; line-height:1.45;
  background:#2a1620; border:1px solid #59303c; color:#ffb3b3;
}
.alert.ok{ background:#12241c; border-color:#265741; color:#9fe8c4; }
.alert.info{ background:#141d2a; border-color:#2b3d55; color:#a9c6e8; }

/* ---------------- sélecteur de programme ---------------- */
.prog-list{ display:flex; flex-direction:column; gap:12px; }
.prog-card{
  display:flex; align-items:center; gap:14px; text-align:left;
  width:100%; cursor:pointer; text-decoration:none;
  background:var(--surface); border:1px solid var(--line); border-radius:16px;
  padding:18px; color:var(--text); font-family:inherit;
  transition:border-color .15s, transform .12s;
}
.prog-card:active{ transform:scale(0.99); }
/* La progression, sur la carte même : on voit où l'on en est avant d'ouvrir.
   Le corps prend la place restante (flex:1, min-width:0) pour que la barre
   s'étire sans pousser la flèche hors de la carte. */
.prog-card .pc-body{ flex:1; min-width:0; display:block; }
.prog-card .pc-fini{
  display:inline-block; margin-left:8px; padding:2px 8px; border-radius:999px;
  font-size:10.5px; font-weight:700; letter-spacing:0.04em; vertical-align:2px;
  color:var(--success); border:1px solid color-mix(in srgb, var(--success) 45%, transparent);
  background:color-mix(in srgb, var(--success) 12%, transparent);
}
.prog-card .pc-bar{
  display:block; height:5px; border-radius:3px; overflow:hidden;
  background:var(--surface-2); border:1px solid var(--line);
  margin:9px 0 5px;
}
.prog-card .pc-bar i{
  display:block; height:100%; border-radius:3px;
  background:linear-gradient(90deg, var(--acc), var(--acc-2));
}
.prog-card .pc-count{
  display:block; font-size:11.5px; color:var(--dim);
  font-variant-numeric:tabular-nums;
}
/* La carte qui porte une discussion perd son arrondi du bas : le lien qui suit
   la prolonge, et les deux se lisent comme un seul bloc. */
.prog-card.avec-fil{ border-bottom-left-radius:0; border-bottom-right-radius:0; border-bottom:none; }
.prog-chat{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  margin-top:-12px;                 /* annule le gap de .prog-list */
  padding:12px 18px; min-height:44px;
  background:var(--surface-2); border:1px solid var(--line);
  border-top:1px solid var(--line);
  border-radius:0 0 16px 16px;
  color:var(--dim); text-decoration:none; font-size:13px; font-weight:600;
}
.prog-chat:hover{ color:var(--text); }
.prog-chat.attend{ color:var(--text); }
.prog-chat .pastille{
  flex:none; min-width:22px; text-align:center;
  background:var(--acc); color:var(--sur-acc);
  border-radius:20px; padding:3px 8px; font-size:11px; font-weight:700; line-height:1;
}
.prog-card .swatch{
  width:44px; height:44px; border-radius:12px; flex:none;
  display:flex; align-items:center; justify-content:center;
  font-weight:800; font-size:17px;
}
.prog-card .pc-title{ font-size:16px; font-weight:700; display:block; }
.prog-card .pc-sub{ font-size:12px; color:var(--dim); display:block; margin-top:3px; }
.prog-card .pc-arrow{ margin-left:auto; color:var(--dim); flex:none; }

/* ---------------- tables (admin) ---------------- */
/* OÙ SUIS-JE, QUAND JE NAVIGUE AU CLAVIER ?
 *
 * Nulle part : le contour du navigateur était perdu sur les fonds sombres, et
 * plusieurs éléments le supprimaient sans rien mettre à la place. Se déplacer
 * à la tabulation revenait à avancer les yeux fermés — pour quelqu'un qui
 * n'utilise pas la souris, l'application était inutilisable.
 *
 * « :focus-visible » et non « :focus » : le navigateur ne le déclenche qu'au
 * CLAVIER. Un bouton cliqué à la souris ne garde donc pas de halo derrière
 * lui, ce qui avait fait retirer les contours d'origine.
 *
 * Deux couches plutôt qu'une : le trait suit la forme de l'élément, le halo
 * le détache d'un fond de même teinte. Sur un bouton coloré, le trait seul
 * disparaîtrait dans sa propre couleur. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible{
  outline:2px solid var(--acc, #22d3ee);
  outline-offset:2px;
  border-radius:8px;
}
.btn:focus-visible, .tab:focus-visible{
  outline:2px solid var(--text);
  outline-offset:2px;
  box-shadow:0 0 0 4px color-mix(in srgb, var(--acc, #22d3ee) 45%, transparent);
}
/* Les cartes de programme sont des liens qui occupent tout leur cadre : le
   contour doit épouser le cadre, pas le texte qu'il contient. */
.prog-card:focus-visible{
  outline:2px solid var(--acc, #22d3ee);
  outline-offset:3px;
}

/* LA TABLE DÉBORDE, ELLE NE POUSSE PAS LA PAGE.
 *
 * Les tables du panel ont six à huit colonnes. Sur un téléphone, elles
 * élargissaient la PAGE ENTIÈRE : le texte des autres sections partait hors
 * de l'écran, et il fallait balayer horizontalement pour lire un paragraphe
 * qui n'avait rien demandé. La règle vivait déjà dans statistiques.php, seule
 * page à l'avoir eue — elle remonte ici, pour toutes.
 *
 * La barre de défilement est masquée : elle rognerait une ligne sur des
 * tables déjà denses, et le geste reste naturel au doigt comme à la souris. */
.table-wrap{
  overflow-x:auto; -webkit-overflow-scrolling:touch;
  scrollbar-width:none; -ms-overflow-style:none;
}
.table-wrap::-webkit-scrollbar{ display:none; }

.table{ width:100%; border-collapse:collapse; margin-top:6px; }
.table th, .table td{ text-align:left; padding:9px 6px; font-size:13px; border-bottom:1px solid var(--line); vertical-align:middle; }
.table th{ font-size:10.5px; text-transform:uppercase; letter-spacing:0.6px; color:var(--dim); font-weight:600; }
/* ─── LE JOURNAL DES INCIDENTS ─────────────────────────────────────
   Un chemin de fichier et une signature se lisent caractere par caractere :
   la chasse fixe est la seule police ou « rn » ne se confond pas avec « m »,
   et ou une colonne de « incidents.php:214 » s aligne toute seule. */
.table td.code{
  font-family:ui-monospace,'JetBrains Mono',Menlo,Consolas,monospace;
  font-size:12px; color:var(--dim); white-space:nowrap;
}
/* La date ne se coupe jamais en deux lignes : « 18.08 » d un cote et
   « 11:15 » de l autre ferait perdre une seconde a chaque lecture. */
.table td.quand{ white-space:nowrap; }
/* La ligne du message, sous celle du resume : elle n a pas de filet, sinon
   chaque incident paraitrait etre deux. */
.table tr.msg td{ border-bottom:none; padding-top:0; padding-bottom:12px; }

.table .tag{
  display:inline-block; font-size:10.5px; padding:2px 8px; border-radius:20px;
  border:1px solid var(--line); background:var(--surface-2); color:var(--dim); margin-right:4px;
}
.table .tag.admin{ color:var(--acc); border-color:var(--acc); }

/* ---------------- onglets d'administration ---------------- */
.page-title{ font-size:20px; font-weight:800; margin:0 0 14px; letter-spacing:-0.2px; }
.tabs{
  display:flex; gap:6px; margin:0 0 16px; padding-bottom:4px;
  /* Sur un écran étroit les onglets ne tiennent pas : ils défilent
     latéralement plutôt que de se replier sur deux lignes. */
  overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none;
}
/* Sur un écran large, ils se REPLIENT au lieu de défiler.
   Avec six onglets, le dernier se retrouvait coupé net au bord de la page :
   un onglet tronqué sur un grand écran passe pour un bogue, et rien n'indique
   qu'il faudrait faire défiler horizontalement — geste que personne ne tente
   à la souris. Replié sur deux lignes, tout est visible d'un coup. */
@media (min-width:700px){
  .tabs{ flex-wrap:wrap; overflow-x:visible; row-gap:8px; }
}
/* Écran étroit, et la rangée déborde vraiment : le bord droit s'estompe pour
   dire qu'il y a une suite. La classe est posée par le script d'admin_tabs()
   seulement quand c'est le cas — un dégradé permanent mentirait une fois la
   rangée arrivée au bout. */
.tabs.deborde{
  -webkit-mask-image:linear-gradient(to right, #000 calc(100% - 26px), transparent);
          mask-image:linear-gradient(to right, #000 calc(100% - 26px), transparent);
}
.tabs::-webkit-scrollbar{ display:none; }
.tab{
  display:inline-flex; align-items:center; gap:8px; flex:none;
  background:var(--surface); border:1px solid var(--line); border-radius:11px;
  padding:10px 14px; color:var(--dim); text-decoration:none;
  font-size:13px; font-weight:600; white-space:nowrap;
}
.tab:hover{ color:var(--text); }
.tab svg{ width:16px; height:16px; flex:none; }
.tab.active{
  color:var(--text); background:var(--surface-2); border-color:var(--acc);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--acc) 18%, transparent);
}
.tab.active svg{ color:var(--acc); }
.tab .count{
  font-size:10.5px; font-weight:700; line-height:1; padding:4px 7px; border-radius:20px;
  background:var(--surface-2); border:1px solid var(--line); color:var(--dim);
}
.tab.active .count{ background:var(--acc); border-color:var(--acc); color:var(--sur-acc); }
/* Pastille de l'onglet e-mails : verte quand l'envoi est réellement opérationnel. */
.tab .dot{ width:7px; height:7px; border-radius:50%; background:var(--line); flex:none; }
.tab .dot.on{ background:var(--success); }

/* ---------------- file de travail ----------------
   Rangée de seaux en tête des onglets Demandes et Programmes. Elle répond à
   « qu'est-ce que je dois faire maintenant ? » quand trente demandes arrivent
   la même semaine. Chaque cellule est un lien qui filtre la liste dessous, et
   doit donc rester une cible confortable au doigt : 44 px de haut au moins. */
.queue-row{
  display:flex; gap:8px; flex-wrap:wrap;
}
.queue-cell{
  display:flex; flex-direction:column; align-items:flex-start; gap:2px;
  flex:1 1 132px; min-width:120px; min-height:44px;
  background:var(--surface-2); border:1px solid var(--line); border-radius:11px;
  padding:10px 13px; text-decoration:none; color:var(--dim);
}
.queue-cell:hover{ color:var(--text); border-color:var(--acc); }
.queue-cell.on{
  color:var(--text); border-color:var(--acc);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--acc) 18%, transparent);
}
/* Un seau vide reste affiché : sa place dans le circuit se lit même à zéro,
   et une rangée dont les cases apparaissent et disparaissent se relit à
   chaque fois. Il s'efface simplement au lieu d'attirer l'œil. */
.queue-cell.vide{ opacity:0.5; }
.queue-cell.vide:hover{ opacity:1; }
.queue-n{ font-size:22px; font-weight:700; line-height:1.1; color:var(--text); }
.queue-cell.vide .queue-n{ color:var(--dim); }
.queue-l{ font-size:11.5px; font-weight:600; letter-spacing:0.2px; }

/* ---------------- carte repliable ----------------
   Un formulaire long qui ne sert qu'a l'occasion — definir un programme de
   zero, quand la plupart naissent d'une demande — n'a pas a occuper l'ecran en
   permanence. Il se replie sur son titre, qui devient la commande : toute la
   ligne est cliquable, et haute de 44 px pour rester une cible confortable au
   doigt. Le repli est purement visuel : le formulaire reste dans le document,
   donc dans l'envoi, ce qui evite le piege des cases masquees. */
.pli > summary{
  list-style:none; cursor:pointer;
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  min-height:44px;
}
.pli > summary::-webkit-details-marker{ display:none; }
.pli > summary:hover .pli-titre{ color:var(--text); }
.pli > summary h2{ margin:0; }
/* Le sous-titre annonce ce que le pli contient : replie, le seul titre ne
   suffit pas toujours a dire si c'est bien la qu'on doit cliquer. */
.pli-quoi{ font-size:12.5px; color:var(--dim); font-weight:500; }
.pli-chevron{
  flex:none; width:18px; height:18px; color:var(--dim);
  transition:transform 0.18s ease;
}
.pli[open] > summary .pli-chevron{ transform:rotate(180deg); }
.pli-corps{ margin-top:16px; }

/* ---------------- fil de discussion ----------------
   Les messages de l'athlète et ceux de son référent. Ce qui rend une
   conversation lisible d'un coup d'œil, c'est de distinguer « moi » de
   « l'autre » : les miens sont alignés à droite et prennent la couleur
   d'accent, les autres restent à gauche sur la surface neutre. */
.fil{
  display:flex; flex-direction:column; gap:8px;
  margin:0 0 14px; max-height:460px; overflow-y:auto;
}
/* En pleine page, la conversation prend la hauteur disponible plutôt qu'une
   valeur fixe : sur téléphone elle occupe l'écran, sur grand écran elle reste
   bornée pour que le champ de saisie n'aille pas se perdre en bas. */
.fil-page{ max-height:min(60vh, 620px); padding-right:4px; }
/* Les BULLES d'une messagerie : les siennes a droite, teintees de l'accent,
   le coin bas rogne du cote de l'auteur — la position dit qui parle avant
   meme la lecture. */
.msg{
  background:var(--surface-2); border:1px solid var(--line);
  border-radius:16px 16px 16px 5px;
  padding:9px 13px 6px; max-width:82%; align-self:flex-start;
}
.msg.mien{
  align-self:flex-end;
  border-radius:16px 16px 5px 16px;
  background:color-mix(in srgb, var(--acc) 12%, var(--surface-2));
  border-color:color-mix(in srgb, var(--acc) 38%, var(--line));
}
.msg-tete{ font-size:11px; margin-bottom:2px; }
.msg-tete strong{ color:var(--acc); font-size:11.5px; font-weight:700; }
/* L'heure et la double coche, en pied de bulle — la date vit dans le
   separateur de jour, et en toutes lettres dans l'infobulle de l'heure. */
.msg-pied{
  display:flex; justify-content:flex-end; align-items:center; gap:4px;
  margin-top:2px; font-size:10.5px; color:var(--dim);
  font-variant-numeric:tabular-nums;
}
/* UNE coche grise : enregistre. DEUX coches couleur : l'autre a ouvert le
   fil (read_at) — la grammaire visuelle des messageries. */
.msg-vu svg{ width:16px; height:16px; display:block; }
.msg-vu{ color:var(--dim); }
.msg-vu.lu{ color:var(--acc); }
.fil-jour{ align-self:center; margin:8px 0 2px; }
.fil-jour span{
  font-size:10.5px; color:var(--dim); background:var(--surface-2);
  border:1px solid var(--line); border-radius:99px; padding:3px 11px;
  text-transform:uppercase; letter-spacing:0.5px;
}
/* pre-wrap : les retours à la ligne de l'auteur sont respectés sans insérer une
   seule balise dans son texte, et les mots trop longs cassent au lieu de faire
   déborder la carte. */
.msg p{
  margin:0; font-size:13.5px; line-height:1.55; color:var(--text);
  white-space:pre-wrap; overflow-wrap:anywhere;
}
.fil-envoi{ margin:0; }

/* ---------------- la discussion en PLEIN ÉCRAN (discussion.php) ----------
   Une messagerie n'est pas une page du site : c'est un écran. Trois étages
   fixes — l'en-tête, le fil qui défile SEUL, la saisie ancrée en bas — et la
   barre de défilement vit dans la gouttière du fil, jamais sur les bulles
   (scrollbar-gutter + le padding du conteneur, pas des bulles). */
.chat-ecran{
  position:fixed; inset:0; z-index:60; display:flex; flex-direction:column;
  background:var(--bg);
}
.chat-tete{
  flex:none; display:flex; align-items:center; gap:10px;
  padding:calc(10px + env(safe-area-inset-top)) 14px 10px;
  border-bottom:1px solid var(--line); background:var(--surface);
}
.chat-retour{ color:var(--text); display:flex; padding:6px; border-radius:10px; }
.chat-retour svg{ width:22px; height:22px; }
.chat-retour:hover, .chat-retour:focus-visible{ background:var(--surface-2); }
.chat-pastille{
  width:38px; height:38px; border-radius:50%; flex:none;
  display:flex; align-items:center; justify-content:center;
  background:color-mix(in srgb, var(--acc) 16%, var(--surface-2));
  border:1px solid color-mix(in srgb, var(--acc) 38%, var(--line));
  color:var(--acc); font-weight:800; font-size:16px; text-transform:uppercase;
}
.chat-qui{ flex:1; min-width:0; }
.chat-qui h1{
  margin:0; font-size:16px; line-height:1.25;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.chat-sujet{
  font-size:11.5px; color:var(--dim);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.chat-alerte{ flex:none; margin:10px 14px 0; }
/* LE SEUL ENDROIT QUI DÉFILE. La gouttière est réservée même sans barre :
   son apparition ne décale plus rien, et les bulles n'y passent jamais. */
.chat-fil{
  flex:1; overflow-y:auto; scrollbar-gutter:stable;
  padding:14px clamp(12px, 3vw, 28px);
}
.chat-fil::-webkit-scrollbar{ width:8px; }
.chat-fil::-webkit-scrollbar-thumb{ background:var(--line); border-radius:99px; }
.chat-fil::-webkit-scrollbar-track{ background:transparent; }
/* Dans l'écran, le fil intérieur ne défile plus : son conteneur s'en charge. */
.chat-ecran .fil, .chat-ecran .fil-page{
  max-height:none; overflow:visible; margin:0; padding-right:0;
}
.chat-ecran .msg{ max-width:min(78%, 560px); }
.chat-vide{ text-align:center; margin-top:34px; }
.chat-saisie{
  flex:none; margin:0; border-top:1px solid var(--line); background:var(--surface);
  padding:8px 10px calc(8px + env(safe-area-inset-bottom));
}
.chat-barre{ display:flex; align-items:flex-end; gap:8px; }
/* Le menu du trombone : une petite colonne posée AU-DESSUS de la barre de
   saisie, comme la feuille de WhatsApp. La saisie est son ancre. */
.chat-saisie{ position:relative; }
.chat-menu{
  position:absolute; bottom:calc(100% + 6px); left:10px; z-index:70;
  display:flex; flex-direction:column; min-width:190px; padding:6px;
  background:var(--surface); border:1px solid var(--line); border-radius:14px;
  box-shadow:0 18px 40px -20px #000;
}
.chat-menu[hidden]{ display:none; }
.chat-menu button{
  display:flex; align-items:center; gap:10px; border:0; background:none;
  cursor:pointer; color:var(--text); font-size:13.5px; font-weight:600;
  padding:10px 12px; border-radius:10px; text-align:left;
}
.chat-menu button:hover{ background:var(--surface-2); }
.chat-menu button svg{ width:19px; height:19px; color:var(--acc); flex:none; }
/* LA PILULE DE COMPOSITION, et le rond d envoi qui reste dehors.
   Tout ce qui sert a ECRIRE tient dans la pilule : emoji a gauche, texte au
   milieu, trombone et appareil photo a droite. Le bouton d envoi est un rond
   plein, separe : composer et expedier sont deux gestes, et les confondre
   fait partir des messages a moitie ecrits. */
.chat-bulle{
  flex:1; min-width:0; display:flex; align-items:flex-end; gap:1px;
  background:var(--surface-2); border:1px solid var(--line);
  border-radius:20px; padding:2px 5px;
}
.chat-bulle:focus-within{ border-color:var(--acc); }
.chat-outil{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:36px; height:36px; margin-bottom:1px; padding:0;
  border:0; background:none; border-radius:50%; cursor:pointer; color:var(--dim);
}
.chat-outil:hover{ color:var(--acc); background:var(--surface); }
.chat-outil svg{ width:21px; height:21px; }
/* hidden DOIT gagner sur le display:flex — les deux boutons naissent caches
   et ne se montrent que si le script tourne. Quatrieme rappel de cette regle
   dans ce fichier : un display de classe bat toujours l attribut. */
.chat-outil[hidden]{ display:none; }
.chat-barre textarea{
  flex:1; min-width:0; resize:none; min-height:36px; max-height:132px;
  border:0; background:none; border-radius:0;
  padding:8px 4px; font-size:14px; line-height:1.45; color:var(--text);
  /* Pas de barre de defilement VISIBLE dans la pilule : elle depassait des
     coins arrondis. Le texte defile toujours au doigt. */
  scrollbar-width:none;
}
.chat-barre textarea:focus{ outline:none; box-shadow:none; }
.chat-barre textarea::-webkit-scrollbar{ display:none; }
/* LE PANNEAU D EMOJI : la meme feuille posee au-dessus de la saisie que le
   menu du trombone, mais pleine largeur — une grille etroite obligerait a
   defiler pour rien. Les caracteres sont ceux du SYSTEME : un emoji envoye
   doit s afficher chez celui qui le recoit, et c est son telephone qui en
   decide. */
.chat-emojis{
  position:absolute; bottom:calc(100% + 6px); left:10px; right:10px; z-index:70;
  max-height:232px; overflow-y:auto; padding:6px 8px 10px;
  background:var(--surface); border:1px solid var(--line); border-radius:14px;
  box-shadow:0 18px 40px -20px #000;
}
.chat-emojis[hidden]{ display:none; }
.chat-emojis-t{
  margin:8px 2px 4px; font-size:13px; line-height:1;
  color:var(--dim); opacity:0.7;
}
.chat-emojis .grille{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(40px, 1fr)); gap:2px;
}
.chat-emojis button{
  border:0; background:none; cursor:pointer; padding:6px 0; border-radius:9px;
  font-size:22px; line-height:1.2;
}
.chat-emojis button:hover{ background:var(--surface-2); }
/* LE SEUL BOUTON QUI AVAIT ÉCHAPPÉ AUX JETONS. Il nommait ses couleurs en dur
   — #7c5cff pour la fin du dégradé, #06111a pour la flèche — et n'a donc pas
   reçu la correction qui a fait passer tous les autres boutons de #06111a à
   #000814. Sur l'extrémité violette, sa flèche tenait 4,38 : sous le seuil de
   4,5. Avec les jetons, elle tient 4,62 sur le violet et 11,12 sur le cyan, et
   elle suivra le mode clair sans qu'on ait à y revenir. */
.chat-envoyer{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:40px; height:40px; border-radius:50%; border:0; cursor:pointer;
  background:linear-gradient(135deg, var(--acc), var(--acc-2)); color:var(--sur-acc);
}
.chat-envoyer svg{ width:19px; height:19px; }
.chat-envoyer:disabled{ opacity:0.55; cursor:default; }
.chat-cadre{ font-size:11px; color:var(--dim); margin:6px 6px 0; }
/* La pièce choisie, avant l'envoi : son nom, son poids, une croix — et la
   jauge de progression, parce qu'une vidéo sur la 4G d'une salle est longue
   et qu'un envoi muet fait rappuyer tout le monde. */
.chat-piece{
  display:flex; align-items:center; gap:8px; margin:0 6px 8px;
  padding:7px 10px; border:1px solid var(--line); border-radius:12px;
  background:var(--surface-2); font-size:12.5px;
}
/* L'attribut hidden DOIT gagner sur le display:flex de la classe — troisieme
   fois que cette panne se presente (pwa-bandeau le 2026-08-11) : sans cette
   ligne, la pastille grise s'affiche vide avant tout choix de fichier, et la
   croix « ne marche pas » puisqu'elle cache un element force visible. */
.chat-piece[hidden]{ display:none; }
.chat-piece-nom{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.chat-piece-x{
  flex:none; border:0; background:none; color:var(--dim); cursor:pointer;
  font-size:18px; line-height:1; padding:2px 6px; border-radius:8px;
}
.chat-piece-x:hover{ color:var(--text); background:var(--surface); }
.chat-jauge{
  flex:none; width:90px; height:6px; border-radius:99px; overflow:hidden;
  background:var(--surface); border:1px solid var(--line);
}
.chat-jauge i{
  display:block; height:100%; width:0;
  background:linear-gradient(135deg, var(--acc), #7c5cff);
  transition:width 0.2s linear;
}
/* Les pièces DANS les bulles. La photo et la vidéo remplissent la bulle en
   respectant ses coins ; le PDF est une carte qui dit son nom et son poids. */
.msg-photo{ display:block; margin:2px 0 4px; }
.msg-photo img{
  display:block; max-width:100%; max-height:340px; border-radius:12px;
}
.msg-video{
  display:block; max-width:100%; max-height:340px; border-radius:12px;
  margin:2px 0 4px; background:#000;
}
.msg-pdf{
  display:flex; align-items:center; gap:9px; margin:2px 0 4px;
  padding:9px 11px; border:1px solid var(--line); border-radius:12px;
  background:var(--surface); color:var(--text); text-decoration:none;
  font-size:13px;
}
/* LA VISIONNEUSE : la photo s'ouvre DANS l'application, par-dessus le fil —
   jamais dans un nouvel onglet, qui sur Android ressemble a une autre
   application. La fleche (et le bouton retour du telephone) referment. */
.chat-voile{
  position:fixed; inset:0; z-index:90; background:rgba(4, 6, 10, 0.96);
  display:flex; align-items:center; justify-content:center;
}
.chat-voile[hidden]{ display:none; }
.chat-voile video{
  max-width:100%; max-height:100%; border-radius:6px; background:#000;
}
.chat-voile video[hidden], .chat-voile img[hidden]{ display:none; }
.chat-voile img{
  max-width:100vw; max-height:100vh; object-fit:contain;
}
.chat-voile-retour{
  position:absolute; top:calc(10px + env(safe-area-inset-top)); left:12px;
  display:flex; align-items:center; justify-content:center;
  width:42px; height:42px; border-radius:50%; border:0; cursor:pointer;
  background:rgba(20, 25, 34, 0.85); color:#e8eef7;
}
.chat-voile-retour svg{ width:22px; height:22px; }
.msg-pdf svg{ width:22px; height:22px; flex:none; color:var(--acc); }
.msg-pdf-nom{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600; }
.msg-pdf-taille{ flex:none; color:var(--dim); font-size:11.5px; }
/* Le libellé du champ existe pour les lecteurs d'écran mais n'a rien à
   afficher : le texte d'invite du champ le dit déjà à ceux qui voient. */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* ---------------- gestion des comptes ---------------- */
.filter-row{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:6px; }
.filter-row input{
  flex:1 1 220px; background:var(--surface-2); border:1px solid var(--line); color:var(--text);
  padding:10px 13px; border-radius:10px; font-size:13.5px; font-family:inherit;
}
.filter-row input:focus{ outline:none; border-color:var(--acc); }
.filter-count{ font-size:11.5px; color:var(--dim); flex:none; }
.user-row[hidden]{ display:none; }
.user-mail{ font-size:11.5px; color:var(--dim); }
.group-label{
  font-size:10.5px; text-transform:uppercase; letter-spacing:0.7px; color:var(--dim);
  font-weight:700; margin:18px 0 8px;
}
.group-label:first-of-type{ margin-top:0; }
/* Pastille d'un programme dans la liste d'attribution. */
.prog-swatch{
  width:36px; height:36px; border-radius:10px; flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  font-size:16px; font-weight:800;
}
.user-row{ border-top:1px solid var(--line); padding:16px 0 4px; }
.user-row:first-of-type{ border-top:none; padding-top:4px; }
.user-head{ display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:8px; margin-bottom:10px; }
.user-head strong{ font-size:15px; }
.user-login{ font-size:11.5px; color:var(--dim); margin-left:7px; }
.user-tags{ display:flex; flex-wrap:wrap; gap:4px; }
.tag{
  display:inline-block; font-size:10.5px; padding:2px 8px; border-radius:20px;
  border:1px solid var(--line); background:var(--surface-2); color:var(--dim);
}
.tag.admin{ color:var(--acc); border-color:var(--acc); }
.tag.warn{ color:#ffcf7a; border-color:#6b5320; }
.inline-form{ margin:0; }
/* Un champ collé à son bouton — le motif d'un remboursement, saisi là où le
   geste se fait. Le demander sur une autre page reviendrait à ne jamais
   l'obtenir. La variante est nommée plutôt que déduite du contenu : les autres
   formulaires en ligne n'ont qu'un bouton, et doivent le rester. */
.inline-form.avec-champ{ display:flex; gap:8px; align-items:center; }
.inline-input{
  background:var(--surface-2); border:1px solid var(--line); color:var(--text);
  padding:9px 12px; border-radius:10px; font-size:13px; font-family:inherit;
  min-width:0; width:min(220px, 42vw);
}
.user-row .checks{ align-items:center; margin-bottom:10px; }

/* ---------------- listes cherchables (comptes, programmes) ---------------- */
.list-bar{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin:0 0 12px; }
.list-bar input[type="search"], .list-bar select{
  background:var(--surface-2); border:1px solid var(--line); color:var(--text);
  padding:9px 12px; border-radius:10px; font-size:13px; font-family:inherit;
}
.list-bar input[type="search"]{ flex:1 1 190px; min-width:0; }
.list-bar select{ flex:0 1 auto; max-width:100%; }
.list-bar input:focus, .list-bar select:focus{ outline:none; border-color:var(--acc); }
.list-bar .btn{ flex:none; }
.list-note{ font-size:11.5px; color:var(--dim); margin:0 0 4px; }

.rowlist{ border-top:1px solid var(--line); margin-top:8px; }
/* Toute la ligne est cliquable : sur un téléphone, viser un bouton « Ouvrir »
   large de soixante pixels est une épreuve. */
.lrow{
  display:flex; align-items:center; gap:11px; padding:13px 4px;
  border-bottom:1px solid var(--line); text-decoration:none; color:var(--text);
}
.lrow:hover{ background:var(--surface-2); }
.lrow-main{ flex:1 1 auto; min-width:0; }
.lrow-name{ display:block; font-size:14.5px; font-weight:700; }
.lrow-sub{ display:block; font-size:11.5px; color:var(--dim); margin-top:3px; line-height:1.45; }
.lrow .user-tags{ flex:none; }
.lrow .chev{ flex:none; color:var(--dim); font-size:19px; line-height:1; }
.lrow .prog-swatch{ width:32px; height:32px; border-radius:9px; font-size:14px; }

.pager{ display:flex; gap:10px; align-items:center; justify-content:center; margin-top:16px; }
.pager .btn{ width:auto; }
.pager span{ font-size:11.5px; color:var(--dim); }

/* Sélecteur de cases à cocher : filtre local, et une hauteur bornée pour qu'un
   catalogue de trois cents programmes ne repousse pas le bouton hors de vue. */
.picker{
  border:1px solid var(--line); border-radius:12px; padding:11px;
  background:var(--surface-2);
}
.picker .picker-filter{
  width:100%; background:var(--surface); border:1px solid var(--line); color:var(--text);
  padding:9px 12px; border-radius:9px; font-size:13px; font-family:inherit;
}
.picker .picker-filter:focus{ outline:none; border-color:var(--acc); }
.picker-list{
  display:flex; flex-wrap:wrap; gap:8px; margin-top:11px;
  max-height:280px; overflow-y:auto;
}
.picker-list .check{ background:var(--surface); }
/* Sans cette ligne, .check{display:flex} l'emporterait sur l'attribut hidden et
   les cases écartées par le filtre resteraient visibles. */
.check[hidden]{ display:none; }
.picker-empty[hidden]{ display:none; }
.picker .user-login{ margin-left:5px; }

.disclosure{ display:inline-block; }
/* Une fois déplié, le panneau prend sa propre ligne : sinon les boutons
   suivants viennent flotter à côté du formulaire ouvert. */
.disclosure[open]{ width:100%; }
.disclosure > summary{ list-style:none; display:inline-block; }
.disclosure > summary::-webkit-details-marker{ display:none; }
.disclosure[open] > summary{ border-color:var(--acc); }
.disclosure-body{
  margin-top:10px; padding:14px; border:1px solid var(--line); border-radius:12px;
  background:var(--surface-2); max-width:360px;
}
.disclosure-body .field:last-of-type{ margin-bottom:10px; }

.field textarea{
  width:100%; background:var(--surface-2); border:1px solid var(--line); color:var(--text);
  padding:11px 13px; border-radius:10px; font-size:14px; font-family:inherit; resize:vertical;
}
.field textarea:focus{ outline:none; border-color:var(--acc); }
.brief-out{
  width:100%; background:#0c1017; border:1px solid var(--line); color:var(--text);
  border-radius:10px; padding:13px; font-size:12.5px; line-height:1.55;
  font-family:ui-monospace,'JetBrains Mono',Menlo,Consolas,monospace; resize:vertical;
}
.brief-out:focus{ outline:none; border-color:var(--acc); }

.settings-grid{ display:grid; gap:0 20px; margin-bottom:6px; }
@media (min-width:620px){ .settings-grid{ grid-template-columns:1fr 1fr; } }
@media (min-width:900px){ .settings-grid{ grid-template-columns:repeat(3,1fr); } }

/* Compact : le pied de page est une signature, pas une section. line-height
   1.9 et 10/12 px de coussin — il trainait 18 px en haut ET en bas, et dans
   l'application la page semblait finir sur du vide. */
.foot{
  text-align:center; font-size:11px; color:var(--dim); line-height:1.9;
  padding:10px 20px calc(12px + env(safe-area-inset-bottom));
}
.foot a{ color:var(--dim); text-decoration:none; border-bottom:1px solid transparent; }
.foot a:hover{ color:var(--text); border-bottom-color:var(--line); }
.foot .sep{ margin:0 7px; opacity:.5; }
/* Instagram : UN LIEN INLINE ORDINAIRE, et le pictogramme descendu de deux
   pixels. « inline-flex » posait le mot deux pixels trop haut par rapport à
   ses voisins : la ligne de base d'une boîte flex est celle de son premier
   élément, et l'icône, centrée verticalement, la tirait vers le haut. Un lien
   inline partage la ligne de base du texte qui l'entoure, sans rien demander. */
.foot-insta{ white-space:nowrap; }
.foot-insta svg{ width:13px; height:13px; vertical-align:-2px; margin-right:4px; }
.foot-insta:hover svg{ color:var(--acc); }

/* ---------------- sélecteur de langue ----------------
   UN DÉROULANT, PAS UNE PASTILLE PAR LANGUE. Les deux pastilles occupaient
   88 px à elles seules ; avec « Se connecter » et « Menu », la barre ne tenait
   plus sur une ligne au téléphone. Le déroulant n'affiche que la langue
   active et range l'autre : 44 px de gagnés, et la place pour une troisième
   langue le jour venu, sans redessiner la barre.

   Il emprunte tout au menu du site — .site-menu, .site-pop, .site-item, et le
   script qui les ferme au clic à côté. Une seconde mécanique aurait dérivé de
   la première à la première retouche. */
.lang-menu .site-pop{ min-width:auto; }
.lang-menu .site-item{ font-size:12.5px; padding:9px 12px; white-space:nowrap; }
.lang-code{
  font-size:11px; font-weight:700; letter-spacing:0.5px;
  color:var(--dim); min-width:19px;
}
.lang-menu > summary .lang-code{ color:var(--text); }
.lang-menu .site-item.on .lang-code{ color:var(--acc); }
/* Le chevron est plus petit que le pictogramme d'un bouton ordinaire : c'est
   un accessoire, pas l'objet du bouton. */
.lang-menu > summary svg{ width:13px; height:13px; color:var(--dim); }
.lang-menu > summary.btn{ gap:3px; padding-left:11px; padding-right:9px; }

/* ---------------- bandeau cookies ---------------- */
.cookie-banner{
  position:fixed; left:12px; right:12px; bottom:12px; z-index:60;
  max-width:760px; margin:0 auto;
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  background:var(--surface); border:1px solid var(--line); border-radius:14px;
  padding:14px 16px; box-shadow:0 22px 50px -26px #000;
}
.cookie-banner[hidden]{ display:none; }
body.has-cookie-banner{ padding-bottom:120px; }
/* Le bouton du bandeau, et LUI SEUL : .btn.small sert partout dans
   l'administration, et l'agrandir globalement changerait des dizaines de
   boutons qu'on ne vise jamais au pouce sur un téléphone. */
.cookie-banner .btn{ min-height:44px; }
.cookie-banner p{ margin:0; flex:1 1 260px; font-size:12.5px; line-height:1.55; color:var(--dim); }
.cookie-banner strong{ color:var(--text); }
.cookie-banner a{ color:var(--acc); }
.cookie-banner .btn{ width:auto; flex:none; }

/* ---------------- pages légales ---------------- */
.legal h1{ font-size:23px; margin:0 0 6px; font-weight:800; }
.legal h2{ font-size:15px; margin:22px 0 8px; color:var(--acc); }
.legal p, .legal li{ font-size:13.5px; line-height:1.7; color:var(--dim); }
.legal p{ margin:0 0 10px; }
.legal ul{ margin:0 0 10px; padding-left:20px; }
.legal strong{ color:var(--text); }
.legal .updated{ font-size:11.5px; color:var(--dim); margin-top:24px; }
.legal table{ width:100%; border-collapse:collapse; margin:6px 0 14px; }
.legal th, .legal td{
  text-align:left; padding:8px 6px; font-size:12.5px; border-bottom:1px solid var(--line);
  color:var(--dim); vertical-align:top;
}
.legal th{ color:var(--text); font-size:10.5px; text-transform:uppercase; letter-spacing:0.6px; }
/* Les 40 px du bas étaient portés par la marque, pas par la barre — voir
   juste en dessous. Ils sont ici désormais : l'espace sous l'en-tête ne change
   pas d'un pixel, seule la marque bouge. */
.topbar{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:40px; }
/* La marque garde une marge basse là où elle ouvre seule une page
   (healthcheck.php) — mais dans une barre du haut elle a un voisin, et cette
   marge la désalignait de lui.

   « align-items:center » centre la boîte AVEC ses marges : les 22 px du bas
   poussaient le logo et le mot 11 px trop haut — exactement la moitié. Le
   décalage se voyait le mieux sur téléphone, où la barre est courte et où le
   « Bonjour » se retrouvait visiblement plus bas que le nom de l'application. */
.topbar .brand{ margin-bottom:0; }
/* LE BLOC DE DROITE SE RANGE À DROITE, même quand il passe à la ligne.
   « .btn-row » enveloppe déjà (flex-wrap), mais alignait chaque ligne à
   GAUCHE : sur un téléphone, le sélecteur de langue se retrouvait au milieu de
   la barre et le « Bonjour Untel » tombait dessous, décalé, sans que rien ne
   soit aligné sur rien. Sur écran large, les deux tiennent sur une ligne et
   cette règle ne change rien. */
.topbar .btn-row{ justify-content:flex-end; }
/* UNE SEULE LIGNE, à toute largeur : marque, bonjour, langue. Le salut
   descendait sous le sélecteur de langue sur téléphone (flex-basis:100%,
   choix d'août 2026) et l'utilisateur ne l'a pas aimé — c'est désormais le
   NOM qui se tronque en points de suspension, jamais la rangée qui plie. */
.topbar .who{
  font-size:12.5px; color:var(--dim);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0;
}
.topbar .who strong{ color:var(--text); }

/* ---- « Appuie encore pour quitter » --------------------------------------
   La racine de l'application installee (choose.php) pose une garde
   d'historique : sans elle, Android quitte au PREMIER retour. Le toast
   annonce le second appui. Pose ici, dans la peau commune : il suit le mode
   clair comme le reste. */
/* Le bandeau d'installation, sur la liste des programmations. Il n'existe que
   si le navigateur a emis beforeinstallprompt : jamais dans l'app installee. */
.pwa-bandeau{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  flex-wrap:wrap; margin:14px 0 4px; padding:12px 14px;
  background:var(--surface-2); border:1px solid var(--line); border-radius:14px;
  font-size:13.5px; line-height:1.5;
}
.pwa-bandeau .pwa-boutons{ display:flex; gap:8px; flex:0 0 auto; }
/* Sans cette regle, le display:flex ci-dessus GAGNE sur le style natif de
   l'attribut hidden, et le bandeau se montre a tout le monde — c'est
   exactement ce qui est arrive le 2026-08-11 avec une classe fantome. */
.pwa-bandeau[hidden]{ display:none; }

/* Les appareils abonnés aux notifications, sur la page Compte. Une liste, pas
   un tableau : trois mots par ligne, et jamais plus de quelques lignes. */
.notif-appareils{ margin:6px 0 0; padding:0; list-style:none; }
/* Le nom a gauche, le retrait a droite : la ligne se lit d'un bout a l'autre,
   et le bouton se trouve toujours au meme endroit quel que soit le nom. */
.notif-appareils li{
  display:flex; align-items:baseline; justify-content:space-between; gap:12px;
  font-size:13px; line-height:1.6; color:var(--dim);
  padding:5px 0; border-bottom:1px solid var(--line);
}
.notif-appareils li:last-child{ border-bottom:0; }
.notif-appareils strong{ color:var(--text); font-weight:600; }
/* « cet appareil » : la seule facon de distinguer deux lignes qui portent le
   meme nom, et c'est le cas courant (deux navigateurs Chrome sur Android). */
.notif-appareils .notif-moi{ color:var(--acc); font-weight:600; }
/* RETIRER EST DESTRUCTIF, et il se dit : cerne, jamais rempli, et il ne prend
   la couleur d'alerte qu'au survol. Un bouton rouge permanent dans une liste
   de trois lignes crie plus fort que tout le reste de la page. */
.notif-retirer{
  flex:none; font:inherit; font-size:12px; font-weight:600; cursor:pointer;
  padding:3px 10px; border-radius:8px;
  background:transparent; border:1px solid var(--line); color:var(--dim);
}
.notif-retirer:hover, .notif-retirer:focus-visible{
  border-color:var(--danger); color:var(--danger);
}
.notif-retirer[disabled]{ opacity:0.5; cursor:default; }
#notif-appareils[hidden]{ display:none; }

/* La carte « Installer l'application » de la page Compte. */
.inst-liste{ margin:6px 0 14px; padding-left:22px; }
.inst-liste li{ font-size:13.5px; line-height:1.6; color:var(--text); margin:4px 0; }

.retour-toast{
  position:fixed; left:50%; bottom:26px; transform:translate(-50%, 8px);
  z-index:90; background:var(--surface-2); color:var(--text);
  border:1px solid var(--line); border-radius:99px; padding:10px 18px;
  font-size:13.5px; font-weight:600; white-space:nowrap;
  opacity:0; pointer-events:none; transition:opacity 0.2s, transform 0.2s;
  box-shadow:0 6px 24px rgba(0,0,0,0.35);
}
.retour-toast.vu{ opacity:1; transform:translate(-50%, 0); }


/* ══════════════════════════════════════════════════════════════════════════
   LA MESSAGERIE, SECONDE COUCHE (2026-08-18) : prevue avant envoi, appui
   long, citations, reactions, vue unique.
   ══════════════════════════════════════════════════════════════════════════ */

/* ─── L ECRAN DE PREVUE. Plein ecran et opaque : ce qu on regarde avant
   d envoyer merite l ecran entier, pas une vignette au-dessus du fil. */
.chat-apercu{
  position:fixed; inset:0; z-index:95; display:flex; flex-direction:column;
  background:var(--bg);
  padding-bottom:env(safe-area-inset-bottom);
}
.chat-apercu[hidden]{ display:none; }
.chat-apercu-tete{
  flex:none; display:flex; align-items:center; gap:6px;
  padding:calc(8px + env(safe-area-inset-top)) 10px 8px;
  border-bottom:1px solid var(--line);
}
.chat-apercu-outils{ margin-left:auto; display:flex; gap:4px; }
/* L INTERRUPTEUR DE LA VUE UNIQUE se voit quand il est arme : sans etat
   visible, on ne sait pas si la photo partira une fois ou pour toujours, et
   c'est la seule decision de cet ecran qu'on ne peut pas reprendre. */
.chat-outil[aria-pressed="true"]{ color:var(--sur-acc); background:var(--acc); }
.chat-apercu-scene{
  flex:1; min-height:0; display:flex; align-items:center; justify-content:center;
  padding:12px; overflow:hidden;
}
/* Le cadre colle EXACTEMENT au media : c'est lui qui ancre le rectangle de
   recadrage, et un cadre plus large decalerait tout le calcul des pixels. */
.chat-ap-cadre{ position:relative; display:inline-block; line-height:0; max-height:100%; }
.chat-ap-cadre canvas, .chat-ap-cadre video{
  display:block; max-width:100%; max-height:64vh; border-radius:10px; background:#000;
}
.chat-ap-doc{
  padding:26px 22px; border:1px solid var(--line); border-radius:14px;
  background:var(--surface-2); font-size:13.5px; line-height:1.5; color:var(--text);
}
.chat-apercu-aide{
  flex:none; margin:0; padding:0 16px 8px; font-size:11.5px; color:var(--dim);
  text-align:center;
}
.chat-apercu-aide[hidden]{ display:none; }
.chat-apercu-pied{
  flex:none; display:flex; align-items:flex-end; gap:8px;
  padding:8px 10px calc(8px + env(safe-area-inset-bottom));
  border-top:1px solid var(--line); background:var(--surface);
}
.chat-apercu-pied textarea{
  flex:1; min-width:0; resize:none; min-height:36px; max-height:110px;
  border:0; background:none; padding:8px 4px; font-size:14px; color:var(--text);
}
.chat-apercu-pied textarea:focus{ outline:none; box-shadow:none; }

/* ─── LE RECTANGLE DE RECADRAGE. Le voile sombre autour vient d'une ombre
   demesuree plutot que de quatre panneaux : un seul element a deplacer, et
   rien ne peut se desynchroniser entre les cotes. */
.chat-crop{
  position:absolute; border:2px solid #fff; border-radius:2px;
  box-shadow:0 0 0 9999px rgba(0,0,0,0.55); cursor:move; touch-action:none;
}
.chat-crop[hidden]{ display:none; }
.chat-crop i{
  position:absolute; width:22px; height:22px; background:#fff; border-radius:50%;
  border:2px solid var(--acc); touch-action:none;
}
.chat-crop i[data-c="nw"]{ left:-11px; top:-11px; cursor:nwse-resize; }
.chat-crop i[data-c="ne"]{ right:-11px; top:-11px; cursor:nesw-resize; }
.chat-crop i[data-c="sw"]{ left:-11px; bottom:-11px; cursor:nesw-resize; }
.chat-crop i[data-c="se"]{ right:-11px; bottom:-11px; cursor:nwse-resize; }

/* ─── LA FEUILLE D ACTIONS de l appui long. Elle monte du bas : c'est la ou
   le pouce arrive, et c'est la que toutes les messageries la posent. */
.chat-actions{
  position:fixed; inset:0; z-index:96; display:flex; align-items:flex-end;
  background:rgba(0,0,0,0.5);
}
.chat-actions[hidden]{ display:none; }
.chat-actions-boite{
  width:100%; max-width:520px; margin:0 auto;
  background:var(--surface); border:1px solid var(--line);
  border-radius:18px 18px 0 0; padding:10px 10px calc(12px + env(safe-area-inset-bottom));
  box-shadow:0 -18px 40px -22px #000;
}
.chat-actions-reacs{
  display:flex; justify-content:space-between; gap:2px;
  padding:4px 2px 10px; border-bottom:1px solid var(--line); margin-bottom:6px;
}
.chat-actions-reacs button{
  flex:1; border:0; background:none; cursor:pointer; font-size:25px;
  line-height:1.2; padding:6px 0; border-radius:12px;
}
.chat-actions-reacs button:hover{ background:var(--surface-2); }
.chat-actions-l{
  display:block; width:100%; text-align:left; border:0; background:none;
  cursor:pointer; color:var(--text); font-size:14.5px; font-weight:600;
  padding:12px 12px; border-radius:12px;
}
.chat-actions-l:hover{ background:var(--surface-2); }

/* Le mot « copie », une seconde et demie. */
.chat-souffle{
  position:fixed; left:50%; bottom:96px; transform:translateX(-50%); z-index:97;
  background:var(--surface-2); color:var(--text); border:1px solid var(--line);
  border-radius:99px; padding:9px 16px; font-size:13px; font-weight:600;
  box-shadow:0 6px 24px rgba(0,0,0,0.35);
}
.chat-souffle[hidden]{ display:none; }

/* ─── LA BANNIERE DE REPONSE, au-dessus de la saisie. */
.chat-reponse{
  display:flex; align-items:center; gap:8px; margin:0 6px 8px;
  padding:7px 10px; border-radius:12px; background:var(--surface-2);
  border-left:3px solid var(--acc);
}
.chat-reponse[hidden]{ display:none; }
.chat-reponse-t{ flex:1; min-width:0; font-size:12.5px; line-height:1.35; }
.chat-reponse-t strong{ display:block; color:var(--acc); font-size:11.5px; }
.chat-reponse-t span{
  display:block; color:var(--dim); overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap;
}

/* ─── LE BLOC CITE DANS LA BULLE. */
.msg-cite{
  display:block; width:100%; text-align:left; cursor:pointer;
  border:0; border-left:3px solid var(--acc); border-radius:8px;
  background:rgba(255,255,255,0.07); padding:6px 9px; margin:0 0 5px;
  font:inherit; font-size:12.5px; line-height:1.35;
}
.msg-cite strong{ display:block; color:var(--acc); font-size:11.5px; }
.msg-cite span{
  display:block; color:var(--dim); overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap;
}
.msg-cite.morte{ color:var(--dim); font-style:italic; cursor:default; border-left-color:var(--line); }
/* Le clignotement du message rejoint : deux battements, et il s'arrete pour
   qui a demande moins de mouvement. */
.msg.clin{ animation:msgclin 0.6s ease-in-out 2; }
@keyframes msgclin{ 50%{ filter:brightness(1.45); } }
@media (prefers-reduced-motion: reduce){ .msg.clin{ animation:none; outline:2px solid var(--acc); } }

/* ─── LES REACTIONS SOUS LA BULLE. Vide, la rangee n'occupe rien : elle est
   toujours rendue pour que le sondage sache ou verser ce qui arrive. */
.msg-reacs{ display:flex; flex-wrap:wrap; gap:4px; margin-top:5px; }
.msg-reacs:empty{ display:none; }
.msg-reacs .reac{
  display:inline-flex; align-items:center; gap:3px; cursor:pointer;
  border:1px solid var(--line); background:var(--surface); color:var(--text);
  border-radius:99px; padding:2px 8px; font-size:13px; line-height:1.4;
}
.msg-reacs .reac i{ font-style:normal; font-size:11px; color:var(--dim); font-weight:700; }
.msg-reacs .reac.mienne{ border-color:var(--acc); background:var(--surface-2); }

/* ─── LA CARTE D UNE VUE UNIQUE. Aucune vignette, jamais : une miniature
   dirait deja ce que la piece montre, et resterait apres l'ouverture. */
.msg-unique{
  display:flex; align-items:center; gap:8px; margin:2px 0 5px;
  padding:10px 12px; border:1px dashed var(--acc); border-radius:12px;
  background:var(--surface-2); font-size:13px; font-weight:600;
  color:var(--text); text-decoration:none;
}
.msg-unique svg{ width:20px; height:20px; flex:none; color:var(--acc); }
.msg-unique.ouvrable{ cursor:pointer; }
.msg-unique.morte{
  border-style:solid; border-color:var(--line); color:var(--dim); font-weight:500;
}
.msg-unique.morte svg{ color:var(--dim); }

/* ══════════════════════════════════════════════════════════════════════════
   iOS ZOOME SUR TOUT CHAMP DE SAISIE SOUS 16 PIXELS (2026-08-19)

   Meme regle que dans assets/program.css, et meme raison : Safari sur iPhone
   agrandit la page a la mise au point d un champ dont la police fait moins de
   16 px, et ne redescend jamais. On pose le plancher au DOIGT seulement, sans
   toucher a la balise viewport — interdire le zoom a la pincee exclurait les
   gens qui en ont besoin, et Apple l ignore de toute facon.

   Liste ecrite a la main : une classe ne dit pas qu elle habille un champ.
   check-design.py §6 la verifie.
   ══════════════════════════════════════════════════════════════════════════ */
@media (pointer: coarse) {
  .field textarea,
  .filter-row input,
  .inline-input,
  .list-bar input[type="search"],
  .list-bar select,
  .suppr-champ input,
  .chat-barre textarea,
  .chat-apercu-pied textarea{ font-size:16px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LA PAGE DE MAINTENANCE (2026-08-19)

   Mesure au navigateur, écran de 375 px : chaque section repliée était une
   carte de 335 × 79 px qui ne contenait qu'un bouton de 173 × 33. Quarante-six
   pixels de vide autour d'un petit bouton, trois fois de suite — parce qu'un
   <summary> portait la classe d'un bouton.

   Un titre de section n'est pas un bouton : il prend la largeur, il se lit en
   premier, et un chevron dit qu'il s'ouvre. La carte se referme alors sur son
   titre au lieu de l'entourer de vide.
   ══════════════════════════════════════════════════════════════════════════ */
.card:has(> .mnt-sec){ padding:0; overflow:hidden; }
.mnt-sec{ display:block; width:100%; }
.mnt-sec > summary{
  list-style:none; cursor:pointer;
  display:flex; align-items:center; gap:10px;
  padding:15px 16px; margin:0;
  font-family:'Poppins', system-ui, sans-serif;
  font-size:15px; font-weight:700; color:var(--text);
}
.mnt-sec > summary::-webkit-details-marker{ display:none; }
.mnt-sec > summary:hover{ background:var(--surface-2); }
.mnt-sec > summary:focus-visible{ outline:2px solid var(--acc); outline-offset:-2px; }
/* LE CHEVRON EST DESSINE, pas ecrit : le marqueur natif de <details> ne se
   style pas de la meme facon sur Safari et sur Chrome, et un caractere de
   police ne pivote pas proprement. */
.mnt-sec > summary::after{
  content:""; flex:none; margin-left:auto;
  width:9px; height:9px; border-right:2px solid var(--dim); border-bottom:2px solid var(--dim);
  transform:rotate(-45deg); transition:transform 0.18s ease;
}
.mnt-sec[open] > summary::after{ transform:rotate(45deg); }
.mnt-sec[open] > summary{ border-bottom:1px solid var(--line); }
/* Le point rouge du retard : contre le titre, pas noyé dedans. */
.mnt-pt{ color:var(--danger); font-size:11px; line-height:1; }
.mnt-sec > div{ padding:14px 16px 16px; }
/* Le titre de la carte des incidents porte le même habit que les trois
   sections repliables, sinon la page a deux façons de dire « section ». */
.mnt-h{
  display:flex; align-items:center; gap:9px;
  font-size:15px; margin:0 0 4px;
}
.mnt-n{
  flex:none; background:var(--surface-2); border:1px solid var(--line);
  border-radius:99px; padding:1px 9px;
  font-size:11.5px; font-weight:700; color:var(--dim);
  font-variant-numeric:tabular-nums;
}

/* ─── LES PASTILLES RESPIRENT. 4 px les faisait se toucher : deux étiquettes
   collées se lisent comme un seul mot coupé. */
.user-tags{ gap:6px; align-items:center; }
.user-tags .tag{ line-height:1.5; }

/* ─── L'EN-TÊTE D'UNE FICHE. Trois étages — nom, adresse, étiquettes — qui
   s'empilaient sans respiration sur un téléphone. Le nom et l'identifiant
   tiennent leur ligne, l'adresse la sienne, les étiquettes la leur. */
.user-head{
  display:flex; flex-wrap:wrap; align-items:flex-start;
  gap:10px 14px; margin-bottom:16px;
}
.user-head > div:first-child{ flex:1 1 220px; min-width:0; }
.user-head strong{ font-size:16px; }
.user-head .user-login{ margin-left:8px; }
.user-mail{ margin-top:3px; word-break:break-word; }
@media (max-width: 560px){
  /* Sous cette largeur, les étiquettes passent sous le nom plutôt que de se
     tasser à sa droite en colonne d'un mot. */
  .user-head > .user-tags{ flex:1 1 100%; }
}

/* ─── LES LIGNES DE LA LISTE. La sous-ligne et les étiquettes se disputaient
   la même largeur : le texte finissait sous les pastilles. */
.lrow-sub{ overflow:hidden; text-overflow:ellipsis; }
.lrow .user-tags{ flex:none; margin-left:8px; }

/* ---------------- la page Exercices du panel ----------------
   Deux cent onze lignes ne se parcourent pas a l oeil : les filtres et la
   recherche sont donc en tete, et chaque carte dit d ou vient son lien.
   Une carte ROUGE veut dire « ce lien ne repond plus » — c est la seule chose
   qu on vient voir en urgence, et elle doit se reperer sans lire. */
.exo-filtres{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:0 0 14px;
}
.exo-filtres input[type="search"]{ flex:1 1 220px; min-width:0; }
.exo-vue{
  padding:6px 12px; border-radius:999px; text-decoration:none;
  border:1px solid var(--line); color:var(--dim); font-size:13px; font-weight:600;
}
.exo-vue.on{ border-color:var(--acc); color:var(--acc); }
/* UNE LIGNE PAR EXERCICE. Depliees, deux cent onze cartes faisaient 42 000 px
   de page : on ne parcourt pas ca. Le repli laisse voir ce qu on vient
   chercher — le nom, d ou vient le lien, son etat — et garde le reste a un
   clic. Un lien mort, lui, s ouvre tout seul (attribut « open » cote PHP). */
.exo-carte{ margin-bottom:6px; padding:0; }
.exo-carte > summary{
  list-style:none; cursor:pointer; display:flex; flex-wrap:wrap;
  align-items:baseline; gap:8px; padding:11px 14px;
}
.exo-carte > summary::-webkit-details-marker{ display:none; }
/* ⚠ LE CHEVRON S ECRIT EN CLAIR, JAMAIS EN ECHAPPEMENT CSS.
   La premiere version portait un echappement hexadecimal vers U+25B8. Il
   s est fait manger en cours de route et il en est reste un OCTAL, donc le
   caractere de controle 0x15, suivi des deux signes « B8 ». Chaque ligne de
   la liste s ouvrait sur un « B8 » bleu, deux cent onze fois.

   Un caractere de controle est INVISIBLE a la relecture : le fichier avait
   l air juste, aucun controle ne bronchait, et c est l utilisateur qui l a
   vu sur son ecran. Le caractere ecrit en clair, lui, ne peut pas se faire
   manger — et check-design.py refuse desormais la famille entiere.
   (Ce commentaire meme, redige avec l echappement, s est fait manger une
   seconde fois : la porte l a attrape aussitot.) */
.exo-carte > summary::before{
  content:"▸"; color:var(--acc); flex:none; align-self:center;
}
.exo-carte[open] > summary::before{ transform:rotate(90deg); }
.exo-carte > summary::before{ display:inline-block; transition:transform .15s ease; }
@media (prefers-reduced-motion: reduce){ .exo-carte > summary::before{ transition:none; } }
.exo-carte:focus-within > summary{ outline:2px solid var(--acc); outline-offset:-2px; }
.exo-nom{ font-weight:700; }
.exo-corps{ padding:0 14px 13px; }
.exo-clef-l{ margin:0 0 6px; }
.exo-clef-l code{ font-size:11.5px; color:var(--dim); }
.exo-carte.mort{ border-left:3px solid var(--danger); }
.exo-tag{
  font-size:10.5px; font-weight:700; letter-spacing:0.6px; text-transform:uppercase;
  padding:2px 8px; border-radius:999px; border:1px solid var(--acc); color:var(--acc);
}
.exo-tag.cat{ border-color:var(--line); color:var(--dim); }
.exo-vid{ margin:6px 0 0; }
.exo-etat{ font-size:12.5px; color:var(--dim); margin:0 0 6px; }
/* La pastille d etat : elle se lit sans etre lue. Le rouge est reserve au lien
   mort — c est la seule chose qu on vient voir en urgence. */
.exo-p{
  flex:none; font-size:11px; font-weight:700; letter-spacing:0.4px;
  padding:2px 8px; border-radius:999px;
  border:1px solid var(--line); color:var(--dim);
}
.exo-p.mort{ border-color:var(--danger); color:var(--danger); }
.exo-p.vu{ border-color:var(--success); color:var(--success); font-variant-numeric:tabular-nums; }
.exo-p.sans{ opacity:.75; }
.exo-mort{ color:var(--danger); }
.exo-form{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; }
.exo-form input[type="url"]{ flex:1 1 260px; min-width:0; }
/* « .btn » fait width:100% par convention du projet, et « .btn.small » est
   l idiome des boutons EN LIGNE. Le « flex:none » verrouille la rangee : sans
   lui, un bouton qui perdrait sa classe « small » reprendrait toute la largeur
   et la carte passerait de cent a quatre cents pixels de haut — multiplie par
   deux cent onze exercices, c est la page entiere qui devient illisible. */
.exo-form .btn{ flex:none; }
