/*
 * Skorio — © 2026 Bilel. Tous droits réservés.
 *
 * Conception, architecture et code : Bilel, fondateur et propriétaire de Skorio.
 * Protégé du seul fait de sa création (CPI art. L111-1 et L112-2 13°) : aucun dépôt
 * n’est nécessaire pour que ce droit existe (Convention de Berne, art. 5.2).
 * Divulgué sous son nom — CPI art. L113-1, présomption de titularité.
 *
 * PÉRIMÈTRE EXACT, et ce que cette mention NE couvre pas — le texte des
 * règlements fédéraux recopié dans model.js, et les enregistrements de app/voix/
 * produits par Google Cloud Text-to-Speech : voir DROITS.md, à la racine.
 *
 * Signature de ce dépôt : SKORIO-2026-3CDCFD9B7357B9DB
 */
*, *::before, *::after { box-sizing:border-box; }

html, body { height:100%; }
body {
  margin:0;
  font-family:var(--sans);
  color:var(--ink);
  background:var(--bg);
  -webkit-text-size-adjust:100%;
  overscroll-behavior-y:none;
}

.app {
  display:flex; flex-direction:column;
  height:100dvh;
  max-width:480px; margin:0 auto;
  background:radial-gradient(130% 60% at 50% -10%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 55%), var(--bg);
  position:relative;
}

.view {
  flex:1 1 auto;
  overflow-y:auto; overflow-x:hidden;
  -webkit-overflow-scrolling:touch;
  /* LA BANDE « NOUVELLE VERSION PRÊTE » NE DOIT RECOUVRIR AUCUN BOUTON (13/09/2026).
     Elle est en `position:fixed` tout en bas, par-dessus la page. Mesuré en jouant
     l'onboarding sur un carnet vierge : `document.elementFromPoint()` au centre du
     bouton « Continuer » de l'étape 2 rend la BANDE, pas le bouton — un tireur qui
     reçoit une mise à jour pendant son inscription ne peut plus avancer. Le remède
     n'est pas de retirer la bande (rien ne se supprime) : c'est de lui réserver sa
     place, pour que le contenu défile au-dessus. `app.js` pose `--maj-barre` à la
     hauteur réelle de la bande au moment où il l'ajoute ; sans bande, le padding
     reste celui d'avant, au pixel près. */
  padding-bottom:calc(14px + var(--maj-barre, 0px));
}
.view:focus { outline:none; }
.view::-webkit-scrollbar { width:0; }

/* ---------- icônes ---------- */
.i {
  width:1em; height:1em; display:inline-block; vertical-align:-.125em;
  fill:none; stroke:currentColor; stroke-width:1.9;
  stroke-linecap:round; stroke-linejoin:round;
  flex:0 0 auto;
}
/* Arabe (01/09/2026) : le flex `row` mirrore déjà la POSITION de chaque icône
   sous `dir="rtl"` — c'est ce qui met « Retour » à droite sans une ligne de
   JS. Mais un `<use>` fige la FORME du tracé : la flèche « ‹ » d'`i-back`
   resterait pointée vers l'ancien bord, faux à l'endroit où elle vient
   d'atterrir. `:has()` cible TOUT appel de `icon('back')` d'un coup, sans
   toucher les 18 fichiers qui l'invoquent — un site oublié serait resté
   droit.

   LE CHEVRON A DEUX EMPLOIS, ET UN SEUL SE MIRRORE — vu à l'écran le 01/09,
   pas déduit : sur l'accueil en arabe, le chevron de « Espace Carabine »
   pointait encore vers la droite alors qu'« entrer dans un écran » se lit
   vers la GAUCHE en RTL.
     · NAVIGATION (entrer dans un écran) → se mirrore, comme la flèche Retour ;
     · VOLET DÉPLIABLE (divulgation) → NE SE MIRRORE PAS : son ouverture pivote
       de 90° quelle que soit la langue (`.row[aria-expanded=true] .chev`,
       pointe toujours vers le BAS), et un miroir composé à cette rotation
       ferait pointer un volet ouvert vers le HAUT.
   L'exclusion vise le chevron qui appartient DIRECTEMENT au bouton dépliable
   (combinateur `>`), jamais un `closest()` : une rangée de NAVIGATION
   imbriquée dans une section repliée des Réglages a un ancêtre
   `[aria-expanded]` et serait exclue à tort — la sonde l'a montré AVANT que
   la règle ne soit écrite. Les autres flèches (réordonnancement, tendance,
   annuler) ne sont pas des conventions de lecture : les mirrorer sans un
   écran vérifié serait deviner. */
[dir="rtl"] svg.i:has(use[href="#i-back"]),
[dir="rtl"] svg.i:has(use[href="#i-chev"]) { transform:scaleX(-1); }
[dir="rtl"] [aria-expanded] > svg.i:has(use[href="#i-chev"]),
[dir="rtl"] [aria-expanded] > .chev > svg.i:has(use[href="#i-chev"]),
[dir="rtl"] [aria-expanded] > .notifchev > svg.i:has(use[href="#i-chev"]) { transform:none; }

/* ---------- accessibilité ---------- */
:focus-visible {
  outline:2px solid var(--accent);
  outline-offset:2px;
  border-radius:6px;
}
.sr {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* ---------- en-têtes ---------- */
.brand { display:flex; align-items:center; justify-content:center; gap:7px; padding:14px var(--pad) 0; }
/* Le logo : DEUX fichiers (blanc pour le sombre, encre pour le clair), tirés
   des pixels de Bilel — jamais de filtre d'inversion, le personnage reste
   noir sur les deux thèmes. La bascule suit data-theme. */
html[data-theme="light"] .logo-sombre { display: none !important; }
html[data-theme="dark"] .logo-clair { display: none !important; }
.brand .bi { width:15px; height:15px; color:var(--accent); }
/* Le wordmark texte d'autrefois (remplacé par le logo le 11/08). Ciblé par sa classe, plus
   par « span » : la devise en porte un (dernier mot soudé à l'impact, audit du 21/08), et
   cette règle le grossissait — « JEDER SCHUSS ZÄHLT » avec ZÄHLT en 13,5 px (capture Bilel, 22/08). */
.brand .wm { font-size:13.5px; font-weight:800; letter-spacing:.22em; text-transform:uppercase; }

.topbar { display:flex; align-items:center; gap:12px; padding:16px var(--pad) 2px; }
.topbar .who { flex:1; min-width:0; line-height:1.15; }
.topbar .who .hi { font-size:12px; color:var(--ink-2); font-weight:500; }
.topbar .who b { display:block; font-size:22px; font-weight:700; letter-spacing:-.02em; margin-top:3px; }

.iconbtn {
  width:44px; height:44px; border-radius:50%;
  background:var(--surface-2); border:1px solid var(--line); color:var(--ink);
  font-size:19px; display:flex; align-items:center; justify-content:center;
  cursor:pointer; flex:0 0 auto;
}
.iconbtn:active { background:var(--surface-3); }
/* 44px : minimum tactile, gants aux mains — vaut pour tous les retours. */
.iconbtn.sq { border-radius:13px; width:44px; height:44px; }

/* Petite icône d'action en bout de ligne : poubelle d'un objectif, d'une
   échéance, d'une arme, d'une évaluation ; coche « entretien fait ».
   Elle reste DISCRÈTE À L'ŒIL — c'est un accessoire de ligne, pas une action
   principale — mais sa CIBLE fait 44 px, étendue par un pseudo-élément, comme
   `.tog` le fait depuis toujours.
   Sept boutons (dont cinq destructifs) tombaient à 34 ou 38 px, trouvés au
   contrôle final du 10/08. Ils portaient leur taille en STYLE INLINE, ce qui
   est exactement ce qui les rendait invisibles aux passes de mise en page —
   le même piège que le sélecteur de thème le 09/08. La taille vit donc ici,
   en CSS, et nulle part ailleurs. */
/* MESURÉ LE 24/09/2026 : la cible promise n'était pas atteignable. Aux quatre
   bords du carré de 44 px, le bas et la droite rendaient `.row` — le
   pseudo-élément était peint SOUS son voisin. Deux corrections, aucune ne
   déplace un pixel (il est en position absolue) : `z-index` le remonte, et
   l'inset vise les 48 px de `--tap` plutôt que les 44 du minimum. */
.iconbtn.sq.mini { position:relative; width:38px; height:38px; font-size:15px; color:var(--ink-3); }
.iconbtn.sq.mini::after { content:''; position:absolute; inset:-5px; z-index:1; }
.iconbtn.sq.mini.xs { width:34px; height:34px; }
.iconbtn.sq.mini.xs::after { inset:-7px; }

.pagehead { display:flex; align-items:center; gap:12px; padding:16px var(--pad) 2px; }
/* `min-width:0` : sans lui, un titre long refusait de se serrer et poussait
   « Modifier » hors de la barre, où il se faisait couper — vu à 220 px, donc
   au zoom fort. Le titre cède, l'action reste entière : c'est elle qu'on
   touche. */
/* `padding-inline-end` : à fort zoom (mesuré à 220 px de large), le titre
   poussé par les boutons se finissait à 3 px du bord et se lisait comme
   tranché. Un titre n'a pas besoin de déborder pour paraître coupé — il
   suffit qu'il touche le bord (constat Bilel, 13/08). Propriété LOGIQUE
   (« fin de ligne », pas « droite ») depuis l'arabe (01/09/2026) : en RTL le
   bord qui presse le titre est le GAUCHE, `padding-right` n'y protégeait
   rien. */
.pagehead h1 { font-size:21px; font-weight:700; letter-spacing:-.02em; margin:0; flex:1; min-width:0; padding-inline-end:4px; }
.pagehead .act { flex:0 0 auto; }
/* « Modifier » du détail d'une séance : c'est le bouton qu'on presse AVEC DES
   GANTS pour corriger un coup. `padding:8px` seul le laissait à 33 px de haut —
   sous le minimum, mesuré à l'audit du 10/08 au soir. Même correctif que
   `.toastact` juste plus bas : la hauteur est imposée, pas déduite du texte. */
.pagehead .act {
  font-size:13px; font-weight:600; color:var(--accent); background:none; border:0;
  cursor:pointer; padding:0 8px; font-family:var(--sans);
  min-height:44px; display:inline-flex; align-items:center;
}

.sect {
  font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--ink-2); font-weight:600; margin:24px var(--pad) 9px;
}
.sect:first-child { margin-top:16px; }

/* Le bloc d'un axe : AUCUNE boîte dessinée — le titre et la carte gardent
   leurs marges au pixel près — mais les événements de ses enfants remontent
   jusqu'à lui, ce qui porte l'appui long d'un seul geste (24/09/2026). */
/* LA CARTE MÉTÉO DU BILAN PORTE SES ICÔNES (24/09/2026, « pas assez visible »).
   Une carte qui montre des dessins se repère au milieu du texte ; un libellé de
   plus s y serait fondu. L aperçu (rien de noté) est à opacité réduite et sans
   fond de pastille : il dit ce qu il y a derrière, jamais que c est coché. */
.meteocarte .meteogi { display:flex; align-self:flex-start; padding-top:2px; color:var(--ink-2); }
.meteocarte .meteogi svg { width:22px; height:22px; }
.meteoapercu { display:flex; gap:10px; margin-top:8px; }
.meteoapercu svg { width:20px; height:20px; color:var(--ink); }
.meteoapercu.vide svg { opacity:.4; }

.axebloc { display:contents; }

/* EN RANGEMENT, LE BLOC DEVIENT UNE VRAIE BOÎTE — `display:contents` ne dessine
   rien et ne peut donc ni se soulever ni se souligner. La carte garde ses
   pastilles : le tireur déplace ce qu'il RECONNAÎT (24/09/2026). */
.axebloc.range { display:block; touch-action:none; }
.axebloc.range .card { outline:1px dashed var(--ink-3, #555); }
.axebloc.range.tenu .card { opacity:.75; transform:scale(.99); }
.rangebar { padding-bottom:8px; margin-bottom:6px; border-bottom:1px solid var(--line, #2a2a2a); }
.rangebar .gi { display:flex; color:var(--ink-2); }

/* ---------- cartes ---------- */
.card {
  margin:0 var(--pad); background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-lg); overflow:hidden;
}
.card + .card { margin-top:10px; }
.card.pad { padding:16px 18px; }

.row {
  width:100%; display:flex; align-items:center; gap:12px;
  padding:15px 16px; border-bottom:1px solid var(--line);
  background:none; border-left:0; border-right:0; border-top:0;
  color:var(--ink); font-family:var(--sans); text-align:start;
}
.card > .row:last-child { border-bottom:0; }
button.row { cursor:pointer; min-height:var(--tap); }
button.row:active { background:var(--surface-2); }
.row .rt { display:block; font-size:15px; font-weight:600; }
.row .rd { display:block; font-size:12.5px; color:var(--ink-2); margin-top:3px; line-height:1.45; }
.row .grow { flex:1; min-width:0; }
.row .chev { color:var(--ink-3); font-size:17px; }
/* Volet ouvert : le chevron pivote au lieu de changer d'icône. Une flèche
   « bas » se lisait comme un téléchargement — le chevron qui tourne est
   l'idiome que tout le monde connaît, et c'est la même icône partout. */
.row[aria-expanded=true] .chev { transform:rotate(90deg); transition:transform .15s; }
/* COMPOSER LA CLASSE DU JOUR (27/09/2026) : en mode « Faire tirer ma classe »,
   la ligne d'un élève ne mène plus à sa fiche — elle se coche. Le chevron cède
   la place à la coche (ui), et l'état se lit au contraste. */
.row.pick[aria-pressed="true"] { background:var(--accent-soft); }
.row.pick[aria-pressed="true"] .rt,
.row.pick[aria-pressed="true"] .chev { color:var(--accent); }
.row .grow input, .row .grow select { margin-top:2px; }
.row .val { font-family:var(--mono); font-size:14px; color:var(--ink-2); }

/* Une ligne « libellé + sélecteur » se REPLIE au lieu de se faire rogner.
 *
 * Constaté par Bilel sur l'Espace, capture à l'appui : « Aucun » disparaissait
 * derrière le bord de la carte. Mesuré : rien ne déborde à 375 px ni même à
 * 260 px — mais `.seg` a un plancher (trois libellés en `white-space:nowrap`,
 * ~156 px), et `.card` est en `overflow:hidden`. En dessous, le troisième
 * choix est COUPÉ, pas décalé : invisible et intouchable.
 *
 * Ce n'est pas un cas de laboratoire. On y arrive par le ZOOM du navigateur —
 * un besoin d'accessibilité, pas une bizarrerie — ou par une fenêtre étroite,
 * et c'est la deuxième fois que ce symptôme est signalé du même côté.
 *
 * Le sélecteur passe donc sous son libellé quand la place manque. Rien ne
 * bouge tant qu'il y a de la place : à 375 px la ligne est identique. */
.row:has(.segcell) { flex-wrap:wrap; row-gap:10px; }
/* 186 px tant qu'ils tiennent — la ligne garde exactement sa densité d'avant.
   Elle NE GRANDIT PAS (sinon elle mangeait le libellé), mais elle peut
   rétrécir : au-delà du repli, il reste les écrans vraiment minuscules. */
.row > .segcell { flex:0 1 186px; min-width:0; }
/* Deux choix au lieu de trois (main directrice, œil directeur) : 160 px. */
.row > .segcell.etroite { flex-basis:160px; }
/* Le libellé garde de quoi rester lisible : sans ce plancher il se réduirait
   à un filet de texte au lieu de provoquer le repli. */
.row:has(.segcell) > .grow { min-width:72px; }
/* Dernier filet, quand même une ligne entière ne suffit plus : les choix
   passent les uns sous les autres. Jamais coupés — un choix invisible est un
   choix intouchable. */
/* (l'enroulement vit désormais sur `.seg` lui-même, plus haut) */
.segcell .seg button { flex:1 1 52px; }

/* ---------- liste réordonnable (méthodes de saisie) ---------- */
/* touch-action:none sur la ligne : sans ça, le doigt qui traîne la poignée
   fait défiler la page au lieu de déplacer la ligne. */
/* Poignée + libellé + deux flèches : 132 px de boutons incompressibles. Sous
   ~240 px, la flèche « descendre » sortait de la carte et s'y faisait rogner —
   or les flèches SONT le chemin accessible du réordonnancement (gants, clavier,
   lecteur d'écran), celui qui doit survivre quand le glisser-déposer échoue.
   Elles passent donc SOUS le libellé au lieu d'être coupées : même réponse en
   trois paliers que `.segcell` (09/08), et la densité ne bouge pas tant qu'il
   y a la place. */
.reorder-row { gap:8px; touch-action:none; flex-wrap:wrap; }
.reorder-row .grow { min-width:96px; }
.reorder-row:last-child { border-bottom:0; }
.reorder-row.dragging { background:var(--surface-2); }
.reorder-row .rt { font-size:14.5px; }
.grip {
  width:44px; height:44px; flex:0 0 auto; padding:0;
  background:none; border:0; color:var(--ink-3); cursor:grab;
  display:flex; align-items:center; justify-content:center;
}
.grip:active { cursor:grabbing; color:var(--ink); }
.reorder-row .iconbtn.sq:disabled { opacity:.35; cursor:default; }

/* ---------- compteur « − 12 + » (coups d'essai, tirs à sec) ---------- */
/* Les deux touches font 44 px chacune et le champ 76 : 132 px incompressibles,
   exactement comme la liste réordonnable. Même réponse, pour la même raison —
   sous ~250 px le « + » sortait de la carte et s'y faisait rogner. */
.cptrow { flex-wrap:wrap; row-gap:10px; }
.cptrow .grow { min-width:104px; }
.cpt { display:flex; align-items:center; gap:8px; flex:0 0 auto; }
/* `input[type=number]` est en `width:100%` — dans un flex c'est sa BASE, et il
   réclamait alors toute la ligne (la leçon de `.btn` dans une carte, 22/09).
   Une base explicite, et le champ fait sa taille. */
.cpt input[type=number] {
  flex:0 0 76px; width:76px; text-align:center;
  padding:13px 4px; font-family:var(--mono); font-size:15px;
}
/* Les flèches natives du champ doublent nos deux touches et font moins de
   20 px : deux cibles tactiles pour la même action, dont une sous la norme. */
.cpt input[type=number] { -moz-appearance:textfield; appearance:textfield; }
.cpt input[type=number]::-webkit-outer-spin-button,
.cpt input[type=number]::-webkit-inner-spin-button { -webkit-appearance:none; margin:0; }
/* Le champ vide dit « Pas compté » — c'est une réponse différente de « 0 », et
   le placeholder doit tenir sans être coupé. */
.cpt input[type=number]::placeholder { font-size:12px; }

/* ---------- pastilles ajoutées par le tireur ---------- */
/* La pastille et son bouton de retrait vont ENSEMBLE : `inline-flex` les colle
   et les fait passer à la ligne d'un seul bloc. Séparés par un retour à la
   ligne, un « × » orphelin semblerait retirer la pastille du dessus. */
.chippair { display:inline-flex; align-items:center; gap:2px; }
/* Elle se distingue du catalogue sans crier : un liseré, pas une couleur — une
   couleur dirait « attention », alors que c'est une réponse comme une autre. */
.chip.perso { border-style:dashed; }
.chip.perso[aria-pressed=true] { border-style:solid; }
/* Le bouton de retrait s'efface tant qu'on ne le cherche pas. */
.chippair .iconbtn.sq.mini.xs { color:var(--ink-3); }
.chippair .iconbtn.sq.mini.xs:hover,
.chippair .iconbtn.sq.mini.xs:focus-visible { color:var(--miss); }
/* La ligne d'ajout d'une réponse : même forme que celle de l'inventaire. */
.card.pad > .row.ajoutpiece { padding-left:0; padding-right:0; border-bottom:0; }

/* ---------- boutons ---------- */
.btn {
  width:100%; border:0; cursor:pointer; font-family:var(--sans);
  font-weight:700; font-size:16px; border-radius:var(--r-md);
  padding:17px; min-height:var(--tap);
  display:flex; align-items:center; justify-content:center; gap:9px;
}
.btn.primary { background:var(--accent); color:var(--on-accent); }
.btn.ghost { background:var(--surface-2); color:var(--ink); border:1px solid var(--line); font-weight:600; }
.btn.danger { background:var(--miss-soft); color:var(--miss); border:1px solid color-mix(in srgb, var(--miss) 40%, transparent); }
.btn:active { filter:brightness(.94); }
.btn[disabled] { opacity:.4; cursor:not-allowed; }
.cta { margin:16px var(--pad) 0; }
.cta + .cta { margin-top:10px; }

/* ---------- segmenté / chips ---------- */
/* Le sélecteur s'ENROULE au lieu de se faire couper. Le 09/08, ce garde-fou
   n'avait été posé que sur `.segcell .seg` — les sélecteurs des Réglages
   (thème) et des conditions de séance (« Mon matériel / Matériel du club »)
   restaient donc coupés au bord de leur carte, sous ~250 px de large ou à fort
   zoom texte. Un défaut corrigé à un seul de ses emplacements n'est pas
   corrigé : la règle vit désormais sur le composant. */
.seg { display:flex; flex-wrap:wrap; background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-sm); padding:3px; gap:3px; }
.seg button {
  /* base 0 + plancher : tant que tout tient, les boutons restent RIGOUREUSEMENT
     de largeur égale, comme avant. C'est le plancher qui déclenche le repli. */
  flex:1 1 0; min-width:52px;
  border:0; background:transparent; color:var(--ink-2);
  font-family:var(--sans); font-size:13px; font-weight:600;
  padding:10px 4px; min-height:46px; border-radius:9px; cursor:pointer;
  /* PAS de `white-space:nowrap` : c'était lui le vrai coupable de la capture
     du 10/08. Le libellé refusait de passer à la ligne, débordait de son propre
     bouton, et la carte le coupait — « Matériel du club » devenait « Matériel du
     cl ». En `normal`, le texte reste sur une ligne tant qu'il y a la place et
     ne se replie que lorsqu'il serait sinon tronqué. Un choix à moitié lisible
     est un choix qu'on n'ose pas toucher. */
  line-height:1.25;
}
.seg button[aria-pressed="true"] { background:var(--accent); color:var(--on-accent); }

/* Barre d'onglets en rangement : on voit que la barre a changé d'état, et le
   doigt déplace au lieu de faire défiler la page. */
.seg.reordering { outline:2px dashed var(--accent-line); outline-offset:3px; }
.seg.reordering button { touch-action:none; background:var(--surface-3); color:var(--ink); }
.seg.reordering button[aria-pressed="true"] { background:var(--accent); color:var(--on-accent); }
.segorder { display:flex; align-items:center; gap:10px; margin-top:10px; }
.segtip { opacity:.75; }

.chips { display:flex; flex-wrap:wrap; gap:8px; }
.chip {
  border:1px solid var(--line); background:var(--surface-2); color:var(--ink-2);
  font-family:var(--sans); font-size:13px; font-weight:600;
  /* 44 px : minimum tactile, gants aux mains — la pastille sert partout
     (pratiques, disciplines, matériel de l'élève, équipement du jour).
     `min-width` autant que `min-height` : une pastille qui ne porte QU'UNE
     ICÔNE — la poubelle d'un lot de munitions — tombait à 42 px de large,
     alors que ce commentaire annonçait 44. Un texte, lui, dépasse toujours :
     la règle ne change rien aux autres. */
  padding:10px 14px; border-radius:22px; cursor:pointer; min-height:44px; min-width:44px;
}
.chip[aria-pressed="true"] { background:var(--accent-soft); color:var(--accent); border-color:var(--accent-line); }
/* UNE PASTILLE QUI PORTE UNE ICÔNE (23/09/2026, demande de Bilel sur la météo).
   Classe à PART, et c'est délibéré : `.chip` sert aussi aux pratiques, aux
   disciplines, au matériel de l'élève et aux douze langues — la passer en
   `inline-flex` aurait changé des écrans que cette demande ne vise pas.
   17 px et non 1em : à 13 px de texte l'icône tombait à 13 px, trop petite pour
   qu'un nuage se distingue d'un soleil voilé. */
.chip.ico { display:inline-flex; align-items:center; gap:7px; }
.chip.ico > svg.i { width:17px; height:17px; }

/* ---------- sommaire d'un écran long (Réglages) ----------
   Une seule rangée qui défile horizontalement plutôt qu'un pavé de pastilles
   qui s'enroule : le sommaire doit coûter une ligne, pas un écran. Collant,
   donc atteignable depuis n'importe quel point des ~5 000 px de la page. */
.somm {
  position:sticky; top:0; z-index:3;
  display:flex; gap:8px; overflow-x:auto; overscroll-behavior-x:contain;
  padding:6px 0 10px; margin-bottom:2px;
  background:var(--bg); border-bottom:1px solid var(--line);
  /* La barre de défilement n'apporte rien ici : le geste est au doigt. */
  scrollbar-width:none;
}
.somm::-webkit-scrollbar { display:none; }
/* `flex:0 0 auto` : sans lui, les pastilles se compriment au lieu de défiler
   et les derniers libellés deviennent illisibles. */
.somm .chip { flex:0 0 auto; font-size:12.5px; padding:8px 13px; min-height:44px; }
/* Un titre visé par le sommaire ne doit pas atterrir DERRIÈRE lui.
   Mesuré : la barre fait 61 px, et `scrollIntoView({block:'start'})` posait le
   titre à −1 px — on tombait donc sous « APPARENCE », au milieu de sa carte,
   avec l'impression que le réglage avait disparu. `scroll-margin-top` réserve
   la hauteur de la barre ; il n'agit QUE sur `scrollIntoView`, donc les écrans
   sans sommaire ne bougent pas d'un pixel. */
.sect { scroll-margin-top:72px; }

/* ---------- interrupteur ---------- */
.tog {
  width:50px; height:30px; border-radius:20px; background:var(--surface-3);
  border:1px solid var(--line); position:relative; cursor:pointer; flex:0 0 auto;
  transition:background .15s;
}
/* L'interrupteur garde sa taille visuelle (50 × 30), mais sa zone tactile est
   étendue à 44 px de haut : on le vise sans le regarder, gants aux mains. */
.tog::after { content:''; position:absolute; inset:-7px -4px; }
/* `inset-inline-start` : le voyant glisse du bord de DÉPART vers le bord de
   FIN, en LTR comme en RTL — un `left` figé aurait fait glisser le voyant
   d'un lecteur arabe vers le bord « éteint », l'inverse du geste qu'il vient
   de faire (arabe, 01/09/2026). */
.tog i { position:absolute; top:2px; inset-inline-start:2px; width:24px; height:24px; border-radius:50%; background:var(--ink-2); transition:.16s; }
.tog[aria-pressed="true"] { background:var(--accent); }
.tog[aria-pressed="true"] i { inset-inline-start:22px; background:var(--on-accent); }

/* ---------- champs ---------- */
/* `password` (15/09/2026) : les deux champs du code de verrouillage (Réglages → Sécurité)
   n'étaient dans aucune règle — 21 px de haut et la police de 13 px du navigateur, sous
   les 44 px de la charte, mesuré à l'écran. */
input[type=text], input[type=number], input[type=date], input[type=time], input[type=password], textarea, select {
  width:100%; background:var(--surface-2); border:1px solid var(--line);
  border-radius:var(--r-sm); color:var(--ink); font-family:var(--sans);
  font-size:16px; padding:13px 14px; min-height:var(--tap);
  /* Un champ a une largeur INTRINSÈQUE (son attribut `size` par défaut) qui,
     dans une grille ou un flex, l'empêche de descendre sous ~180 px : à 220 px
     il débordait alors de sa carte de 8 px, et `overflow:hidden` le rognait.
     `width:100%` ne suffit pas — c'est le minimum qu'il faut lever. */
  min-width:0;
}
/* Un libellé trop long finit en « … » et non coupé net au milieu d'un mot :
   une coupure franche se lit comme un écran cassé. Le nom complet, lui, est
   écrit sous le champ — voir `selectNomme()` dans ui.js, car l'ellipse seule
   perdrait ce qui distingue deux épreuves quand le discriminant est à la fin. */
select { text-overflow:ellipsis; }
textarea { min-height:96px; resize:vertical; line-height:1.5; }
input::placeholder, textarea::placeholder { color:var(--ink-3); }

/* Deux ou trois champs côte à côte. `1fr 1fr` en dur ne peut PAS rétrécir
   sous la largeur intrinsèque de son contenu — un champ « date » a un gabarit
   natif large, et la grille débordait donc de sa carte, qui la coupait. Avec
   `auto-fit` + `minmax`, les champs passent l'un sous l'autre au lieu d'être
   rognés. Au-dessus du seuil, la disposition est rigoureusement celle d'avant. */
/* `minmax(128px, 1fr)` pose un PLANCHER que la colonne ne franchit pas : dès
   que la carte est plus étroite que 128 px — un zoom fort, une fenêtre de
   220 px — la colonne dépassait de sa carte et s'y faisait rogner. `min()`
   lève le plancher quand il n'y a plus la place : la colonne ne descend jamais
   sous 128 px tant qu'ils existent, et cède quand ils n'existent plus. */
.duo { display:grid; grid-template-columns:repeat(auto-fit, minmax(min(128px, 100%), 1fr)); gap:10px; }
.trio { display:grid; grid-template-columns:repeat(auto-fit, minmax(min(96px, 100%), 1fr)); gap:8px; }
.duo > *, .trio > * { min-width:0; }

input[type=range] {
  -webkit-appearance:none; appearance:none; width:100%; height:40px;
  background:transparent; cursor:pointer;
}
input[type=range]::-webkit-slider-runnable-track { height:8px; border-radius:20px; background:var(--surface-3); }
input[type=range]::-moz-range-track { height:8px; border-radius:20px; background:var(--surface-3); }
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance:none; width:30px; height:30px; border-radius:50%;
  background:var(--accent); border:3px solid var(--surface); margin-top:-11px; box-shadow:var(--shadow);
}
input[type=range]::-moz-range-thumb { width:30px; height:30px; border-radius:50%; background:var(--accent); border:3px solid var(--surface); }

/* ---------- navigation basse ---------- */
.nav {
  flex:0 0 auto; display:flex; align-items:center;
  background:var(--surface); border-top:1px solid var(--line);
  padding:8px 6px calc(14px + env(safe-area-inset-bottom));
}
.nav button {
  flex:1; background:none; border:0; cursor:pointer;
  display:flex; flex-direction:column; align-items:center; gap:4px;
  color:var(--ink-3); font-family:var(--sans); font-size:11px; font-weight:600;
  padding:6px 0; min-height:var(--tap);
}
/* CINQ BOUTONS TIENNENT — À DEUX DÉCLARATIONS PRÈS, ET ELLES SONT INDISSOCIABLES
   (13/09/2026, demande Bilel : « qu'il y ait 5 icônes en bas au total »).

   `.nav button` est en `flex:1` SANS `min-width:0` : sa taille minimale automatique est
   donc sa largeur min-content, c'est-à-dire son mot le plus long. Chaque bouton refuse
   de descendre sous « Historique » (52 px), « Wettkämpfe » (61 px) ou, pire,
   « Maßgeschneidert » (87 px, insécable). À cinq boutons, l'allemand débordait de 31 px
   hors de l'écran dès 280 px — un défilement horizontal de page, que la charte interdit.
   Rien ne le rattrapait : `.app` et `.nav` sont `overflow:visible`, et le
   `.view{overflow-x:hidden}` est un FRÈRE de `.nav`, pas son parent.

   **`min-width:0` SEUL AGGRAVE LE DÉFAUT** — mesuré : le bouton rétrécit bien, mais le
   mot insécable sort alors de sa boîte et les libellés se chevauchent ; l'allemand
   déborde dès 382 px au lieu de 310. Les deux déclarations se posent ensemble ou pas du
   tout. Avec les deux : plus aucun débordement de 480 px à 140 px, dans les trois
   langues mesurées, et des largeurs égales.

   Le `:not(.addbtn)` est requis : le bouton central porte `.fab` et `.lbl`, qui ne
   doivent ni rétrécir ni se tronquer. Le projet connaît déjà ce patron — `.duo > *`
   et `.trio > *` portent `min-width:0` plus haut dans ce fichier. */
.nav button { min-width:0; }
.nav button:not(.addbtn) > span {
  max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}

/* LE BOUTON « SÉANCE » EST TOUJOURS AU MILIEU (13/09/2026, demande de Bilel).

   Avant : un seul rang de boutons, tous `flex:1`. Le bouton central n'était donc centré
   que par ACCIDENT DE COMPTAGE — quand il y avait autant de boutons de chaque côté. À
   trois icônes il l'était, à cinq aussi, mais à QUATRE il partait à 66 px du milieu :
   l'action principale de l'app changeait de place selon ce que le tireur avait visité la
   veille.

   `flex:1 1 0` — avec une base ZÉRO, jamais `auto` : c'est ce qui rend les deux moitiés
   rigoureusement égales quel que soit leur contenu. Avec `flex:1` (base `auto`), la
   moitié la plus chargée réclamerait plus de place et le centre repartirait.

   `min-width:0` sur le groupe autant que sur le bouton : sans lui, la taille minimale
   automatique du groupe serait la somme des min-content de ses boutons, et la moitié la
   plus chargée repousserait de nouveau le centre. */
.nav .navside { flex:1 1 0; min-width:0; display:flex; align-items:center; }
.nav .navside > button { flex:1 1 0; min-width:0; }
.nav button .ni { font-size:22px; }
.nav button.on { color:var(--accent); }
/* `.nav button { display:flex }` bat le `[hidden]` par défaut du navigateur —
   un AUTEUR l'emporte toujours sur la feuille de style de l'UA, quelle que
   soit la spécificité. Trouvé en testant le widget masqué de la barre : il
   restait affiché malgré l'attribut. Sans cette règle, aucun bouton `hidden`
   de la barre ne disparaît jamais. */
.nav button[hidden] { display:none; }
.nav .addbtn { flex:0 0 auto; color:var(--accent); width:auto; padding:0 10px; }
.nav .addbtn .fab {
  width:54px; height:54px; border-radius:50%; background:var(--accent); color:var(--on-accent);
  display:flex; align-items:center; justify-content:center; margin-top:-16px; box-shadow:var(--shadow);
}
.nav .addbtn .fab .ni { font-size:27px; }
.nav .addbtn .lbl { font-size:11px; font-weight:700; color:var(--accent); }
.app[data-chrome="off"] .nav { display:none; }

/* ---------- ranger la barre du bas au doigt (14/09/2026) ---------- */
/* Demande de Bilel : « je veux pouvoir faire un appui long et les ranger dans
   l'ordre que je veux ». MÊME GESTE, AU MOT PRÈS, que les onglets de saisie
   (`.seg.reordering`, score-points.js, 13/08) : appui long, puis glisser OU deux
   flèches, puis « Terminé ». *Le tireur qui a rangé l'un sait ranger l'autre* —
   c'est la règle que ce projet s'applique depuis le rangement des Réglages.

   RIEN ICI NE CHANGE UNE LARGEUR NI UNE HAUTEUR, et c'est la condition pour que
   le bouton « Séance » reste au milieu : `outline` ne participe pas à la mise en
   page (contrairement à `border`), et les places vides qui se montrent sont des
   boutons `flex:1 1 0` dans des groupes `flex:1 1 0` — chaque moitié prend la
   moitié de ce qui reste, qu'elle porte zéro, un ou deux boutons. Mesuré : écart
   au centre 0,00 px aux dix combinaisons de places visibles.

   `touch-action:none` : sans lui, le doigt qui traîne sur la barre fait défiler
   la page au lieu de déplacer — la leçon de `.reorder-row`, payée le 08/08. */
.nav.ranger .navslot {
  touch-action:none; outline:1px dashed var(--accent-line); outline-offset:-3px; border-radius:12px;
}
/* UNE PLACE VIDE SE MONTRE PENDANT LE RANGEMENT. Sans ça on ne pourrait rien y
   déposer, et une place laissée vide est une place — pas un trou. */
.nav.ranger .navslot[hidden] { display:flex; opacity:.4; }
.nav.ranger .navslot[aria-pressed="true"] { outline-style:solid; color:var(--accent); }

/* La bande de rangement : posée AU-DESSUS de la barre, jamais dessus — les deux
   doivent rester tapables, comme `.majbar`. Elle porte les deux flèches (le seul
   moyen qui marche avec des gants, au clavier et au lecteur d'écran) et la
   sortie. z-index au-dessus de `.majbar` : si une mise à jour s'annonce pendant
   qu'on range, c'est la bande qu'on doit pouvoir toucher. */
.navorder {
  position:fixed; left:50%; bottom:calc(96px + env(safe-area-inset-bottom));
  transform:translate(-50%, 0);
  background:var(--surface-3); color:var(--ink); border:1px solid var(--line);
  border-radius:14px; padding:6px 10px; box-shadow:var(--shadow); z-index:62;
  /* vw-ok : position:fixed, mesurée par rapport à la FENÊTRE, comme `.toast`. */
  max-width:min(94vw, 420px);
  display:flex; align-items:center; gap:8px;
}
.navorder .note { margin:0; flex:1; text-align:center; min-width:0; }
/* 48 px (`--tap`) et non 44 : c'est le plancher de la BARRE, et cette bande sert le même
   geste, au même endroit, avec les mêmes gants. `.toastact` et `.majbar button` restent
   à 44 — ils ne servent qu'à répondre à une notification, pas à viser quatre fois de
   suite. Mesuré ici même : la bande rendait 44. */
.navorder button { min-height:var(--tap); }
.navorder .iconbtn.sq { min-width:var(--tap); }

/* ---------- notification brève ---------- */
.toast {
  position:fixed; left:50%; bottom:calc(96px + env(safe-area-inset-bottom));
  transform:translate(-50%, 14px);
  background:var(--surface-3); color:var(--ink); border:1px solid var(--line);
  border-radius:14px; padding:12px 18px; font-size:13.5px; font-weight:600;
  box-shadow:var(--shadow); opacity:0; pointer-events:none;
  /* vw-ok : la notification est en position:fixed, donc positionnée par rapport
     à la FENÊTRE — c'est bien elle qu'il faut mesurer ici, pas une carte. */
  transition:opacity .18s, transform .18s; z-index:60; max-width:min(90vw, 420px); text-align:center;
  display:flex; align-items:center; justify-content:center; gap:16px;
  /* LARGEUR AU CONTENU (06/10/2026, mesuré à 375 px). Positionnée à left:50%, une
     notification en position fixe ne disposait que de la MOITIÉ droite de la fenêtre
     pour se dimensionner : 188 px sur 375, « Séance supprimée · Annuler » sur deux
     lignes, un texte long sur SIX. max-content la laisse prendre sa largeur, et
     max-width ci-dessus la borne : un et trois lignes, centrée à 18 px des bords. */
  width:max-content;
}
.toast.show { opacity:1; transform:translate(-50%, 0); }
/* Une notification laisse passer les clics au travers ; celle qui porte un
   bouton doit évidemment les recevoir. */
.toast.act { pointer-events:auto; padding:6px 10px 6px 18px; }
.toastact {
  flex:0 0 auto; background:none; border:0; cursor:pointer; font-family:var(--sans);
  font-size:13.5px; font-weight:700; color:var(--accent);
  padding:0 12px; min-height:44px; border-radius:10px;   /* 44 px : gants aux mains */
}
.toastact:active { background:var(--surface-2); }

/* ---------- mise à jour prête ---------- */
/* La barre qui annonce une nouvelle version (app.js, 14/08). Elle RESTE — un
   toast s'efface pendant qu'on regarde ailleurs, et une mise à jour ratée a
   coûté quatre allers-retours dans la seule journée du 14/08. Posée AU-DESSUS
   de la barre de navigation, jamais dessus : les deux doivent rester tapables. */
.majbar {
  position:fixed; left:50%; bottom:calc(96px + env(safe-area-inset-bottom));
  transform:translate(-50%, 0);
  background:var(--accent); color:var(--on-accent);
  border-radius:14px; padding:6px 10px 6px 18px; font-size:13.5px; font-weight:600;
  box-shadow:var(--shadow); z-index:61;
  /* vw-ok : position:fixed, mesurée par rapport à la FENÊTRE, comme le toast. */
  max-width:min(90vw, 420px);
  display:flex; align-items:center; gap:14px;
}
.majbar button {
  flex:0 0 auto; background:none; border:0; cursor:pointer; font-family:var(--sans);
  font-size:13.5px; font-weight:800; color:var(--on-accent); text-decoration:underline;
  padding:0 12px; min-height:44px; border-radius:10px;   /* 44 px : gants aux mains */
}

/* ---------- grand écran : la colonne à droite ---------- */
/* Demande Bilel (14/08/2026) : « mets le projet à droite de l'écran ».

   Le cas d'usage est celui de sa journée : la feuille de match — photo, PDF,
   ou papier posé à côté — occupe la gauche de l'écran pendant qu'on recopie ou
   qu'on relit dans l'app. Centrée, la colonne laissait deux marges mortes ;
   à droite, la moitié gauche reste utilisable pour le document qu'on compare.

   900px et pas moins : en dessous, ranger à droite ne libérerait qu'une bande
   inutilisable — autant rester centré. Sur téléphone, rien ne change.

   Les éléments en `position:fixed` (notification, barre de mise à jour) sont
   ancrés à la FENÊTRE : sans correction ils resteraient au milieu du vide,
   détachés de la colonne qu'ils commentent. `right:240px` + `translate(50%)`
   les recentre sur la colonne de 480px rangée à droite.

   CE BLOC VIT APRÈS `.toast` ET `.majbar`, ET CE N'EST PAS UN RANGEMENT : à
   spécificité égale, c'est la DERNIÈRE règle qui gagne. Écrit d'abord en tête
   de feuille, il était silencieusement écrasé par les définitions qu'il
   voulait corriger.

   ARABE (01/09/2026) — CE BLOC RESTE PHYSIQUE, DÉLIBÉRÉMENT, alors que tout
   le reste de la feuille est passé en propriétés logiques. Trois raisons :
   (1) la raison d'être de ce rangement est un poste de travail (« la feuille
   de match occupe l'autre moitié »), pas un sens de lecture — rien dans la
   demande de Bilel ne dépend de la langue ; (2) le mirorer voudrait dire
   retourner TROIS choses couplées — la marge, l'ancrage `right` et le SIGNE
   du `translate(50%)`, que `inset-inline-*` ne retourne pas — pour une
   préférence que personne n'a exprimée ; (3) laissé tel quel, l'ensemble
   reste COHÉRENT : la colonne est à droite et la notification reste centrée
   dessus, dans toutes les langues. Le cas de base (téléphone) est symétrique
   (`left:50%` + `translate(-50%)`) et n'a donc rien à retourner. À trancher
   avec Bilel le jour où un tireur arabophone travaille sur grand écran. */
@media (min-width: 900px) {
  .app { margin:0 0 0 auto; }
  .toast { left:auto; right:240px; transform:translate(50%, 14px); }
  .toast.show { transform:translate(50%, 0); }
  .majbar { left:auto; right:240px; transform:translate(50%, 0); }
}

/* ---------- états vides ---------- */
.empty { margin:8px var(--pad) 0; text-align:center; padding:34px 18px; }
.empty .ic { font-size:44px; color:var(--ink-3); opacity:.55; }
.empty h2 { font-size:17px; font-weight:700; margin:14px 0 0; letter-spacing:-.01em; }
.empty p { font-size:13.5px; color:var(--ink-2); line-height:1.6; margin:8px 0 0; }

.note { font-size:12.5px; color:var(--ink-2); line-height:1.6; margin:10px var(--pad) 0; }
.mono { font-family:var(--mono); }

/* ---------- replié ---------- */
/* Un panneau replié À L'ÉCRAN mais qui doit SORTIR EN PDF — les synthèses de
   compétition. L'attribut `hidden` ne conviendrait pas : la feuille
   d'impression doit pouvoir le rouvrir, et un document exporté amputé de ce
   qu'on n'avait pas déplié serait un document faux. */
.plie { display:none; }
