/* RPrime — la feuille de style, seule et entière.
 *
 * Une déclaration par ligne : c'est plus long à lire d'un coup d'œil, mais un diff montre alors
 * quelle propriété a changé, et pas « la ligne 42 ». Sur un front sans build, le diff est le seul
 * outil de relecture qu'on ait.
 *
 * Les couleurs passent TOUTES par les variables ci-dessous. Une valeur écrite en dur dans une règle
 * est une couleur qui ne suivra pas le jour où le thème change, et c'est celle-là qu'on ne retrouve
 * plus. Les rares exceptions sont les couleurs propres à une signification (priorités, états), et
 * elles sont commentées là où elles sont.
 *
 * Ne couvre que les écrans qui existent — Projets, Tickets, la fiche d'un ticket (#777), le plan
 * d'action (#779), l'Annuaire et les Paramètres — l'écran de pilotage sur téléphone (#768) est
 * retiré (#1073), et son style avec lui. Le calendrier et les indicateurs viendront avec les tickets qui les
 * apportent : écrire leur style d'avance, ce serait entretenir des règles pour des écrans qui
 * n'existent pas.
 */

:root {
  /* Le thème est SOMBRE, et il le dit au navigateur (#918). Sans cette ligne, tout ce que la page ne
     dessine pas elle-même reste clair : les listes déroulantes des Paramètres s'ouvraient en blanc,
     les cases à cocher en bleu système, et les barres de défilement en gris clair — trois contrôles
     qu'aucune règle de cette feuille ne visait, donc trois écarts qu'aucune relecture de la feuille ne
     montrait. `accent-color` fait prendre l'accent aux cases et aux curseurs natifs. */
  color-scheme: dark;
  accent-color: var(--accent);
  /* LA PALETTE RPRIME (#884, #948) : un brun très sombre, un texte crème, et l'OR de la marque.
     #884 avait posé une nuit bleutée et un accent CYAN, pour une raison qui tient encore à moitié :
     l'ambre DIT DÉJÀ « attention » dans cette feuille (--warn, la priorité 2, la file de tri,
     « prendre », un doublon, une clé révélée). #948 a retiré les bleus à la demande — les colonnes,
     « Nouveau ticket », la pastille « Tous les projets » et le bandeau de sélection y étaient tous
     peints, et rien ne s'y accordait avec l'en-tête or et brun. L'accent est donc un or LAITON,
     désaturé et plus pâle que l'ambre des alertes : c'est la saturation qui sépare « ce qu'on peut
     toucher » de « attention », et non plus la teinte. Un avertissement qui se confondrait avec un
     lien se corrige ici, sur ces deux jetons, et nulle part ailleurs.
     Ces deux-là sont aussi les SEULS que le front surcharge à l'exécution (#968) : réduit à un
     projet, l'écran prend la couleur de CE projet, éclaircie juste assez pour rester lisible. Les
     valeurs ci-dessous sont donc le DÉFAUT, et non ce qu'on lit forcément à l'écran.
     `--sur-accent`, lui, n'est jamais surchargé — le plancher de lisibilité du front le garde
     valable, et `verifier_front` le mesure au lieu de le croire. */
  /* fonds, du plus profond au plus proche — des bruns chauds, jamais un gris-bleu */
  --bg: #0e0b08;
  --panel: #18130e;
  --panel2: #221b14;
  --line: #3a2f22;
  /* les deux fonds des barres collées en haut (en-tête et onglets) : écrits une fois ici, parce que
     l'en-tête du bureau et celui du téléphone les partagent */
  --barre-haut: #231a11;
  --barre-bas: #110d09;
  /* textes, du plus lisible au plus discret */
  --txt: #f2ebdd;
  --txt2: #d8d0c1;
  --muted: #aea696;
  --muted2: #8e877a;
  /* accents et significations */
  --accent: #d6b26e;
  --accent-soft: rgba(214, 178, 110, .13);
  /* ce qui s'écrit SUR un fond d'accent : l'or est clair, un texte blanc n'y se lirait pas */
  --sur-accent: #1c140a;
  --marque: #e9b44c;
  --ok: #22c55e;
  --warn: #f59e0b;
  --bad: #ef4444;
  /* LES ENCRES des badges et des petits textes qui signifient (#1096) : la même signification que
     les quatre couleurs ci-dessus, écrite en TEXTE sur un fond du thème ou sur le voile teinté d'un
     badge. En sombre, des pastels ; en clair, des encres sombres — un pastel écrit en dur dans une
     règle tenait 8 de contraste ici et 1,0 à 1,9 sur un crème. `verifier_front` mesure chacune dans
     les deux thèmes, sous le voile du badge qui la porte. `transfert` est le bleu d'un passage de
     main (#856), `test` le violet de « En test » : deux sens qui n'ont pas de couleur ci-dessus. */
  --encre-ok: #86efac;
  --encre-bad: #fca5a5;
  --encre-warn: #fcd34d;
  --encre-transfert: #7dd3fc;
  --encre-test: #c084fc;
  /* LES RÔLES d'une session sur sa carte de Terminus (#1230) : PILOTE, BILAN, ÉPREUVE, SAUT. La
     teinte du TYPE qui porte chaque rôle (plan, étude, recette, déploiement — `TYPES` dans js/types.js),
     éclaircie en pastel ici et foncée en encre dans le thème clair : ≥ 8,5 sur les cinq fonds. */
  --encre-pilote: #f0abfc;
  --encre-bilan: #c4b5fd;
  --encre-epreuve: #5eead4;
  --encre-saut: #a5b4fc;
  /* LES TYPES de ticket qui ne portent aucun rôle (#1979) : le robot d'une carte de Terminus prend la
     teinte du type de SON ticket, et ces huit-là n'avaient pas d'encre — ils tombaient tous sur
     l'accent. Même recette que les rôles : la teinte de `TYPES`, en pastel ici (≥ 8,9 sur les fonds). */
  --encre-type-feat: #86efac;
  --encre-type-bug: #fca5a5;
  --encre-type-mco: #dcb893;
  --encre-type-doc: #fcd34d;
  --encre-type-spec: #bef264;
  --encre-type-macro: #7dd3fc;
  --encre-type-echeance: #fda4af;
  --encre-type-autre: #cfc6b6;
  /* le cyan d'un ASSISTANT (#2063), l'agent libre à consigne nommée : ni l'or de l'accent, que gardent les agents libres, ni l'encre d'un type */
  --encre-assistant: #67e8f9;
  /* « En cours » : un vert d'eau et non plus le bleu ciel d'origine (#948) — ni l'or de l'accent,
     ni le vert franc de « Fait » */
  --info: #6cc5ae;
  /* le filet des éléments neutres qu'on veut CREUX (un skill hors catalogue) et le fond de la P3 :
     écrits une fois ici, là où ils étaient deux bleus recopiés en dur */
  --neutre: #4a3d2e;
  --sur-neutre: #efe4d0;
  /* L'ÉTOILE (#1079) : le blanc bleuté des étoiles du logo (leur #8FDCEB, éclairci vers le blanc).
     Une pointe froide dans une palette toute en or et en brun, et une seule : le NOM et le fond
     d'étoiles, rien d'autre — posée sur un bouton ou un lien, elle se confondrait avec un état.
     `--lueur-etoile` est le halo du nom ; il n'existe qu'en sombre, une lueur ne se voit pas sur
     un crème.
     LE CIEL (#1098) peint le fond d'étoiles (`body::before`), et lui seul : un jeton à part de
     `--etoile`, parce que le nom doit tenir un contraste de TEXTE et que le fond, lui, doit se lire
     comme un dessin teinté — un presque-blanc à faible opacité donnait un gris neutre en nuit, et
     l'ardoise du nom un gris verdâtre de poussière sur le crème. Ici un bleu ciel : les traits
     composés tirent au bleu nuit au lieu du gris. `--decor` en est l'opacité ; `--ciel` et `--decor`
     sont les deux seuls réglages à toucher si le fond paraît trop présent, trop effacé ou mal teint. */
  --etoile: #d4f0fa;
  --lueur-etoile: rgba(143, 220, 235, .35);
  --ciel: #8fc4f2;
  --decor: .34;
  /* le voile derrière une boîte de dialogue */
  --voile: rgba(8, 6, 3, .66);
  /* la police du NOM, et de lui seul (cf. `@font-face` plus bas) */
  --police-marque: "Orbitron", "Segoe UI", system-ui, sans-serif;
  /* La HAUTEUR de la barre du haut, écrite une fois (#918) : les onglets se collent juste dessous.
     Elle était dite deux fois — un `top: 59px` recopié à la main sous une barre qui mesurait en
     réalité 63,7 px —, et les onglets la chevauchaient donc de près de cinq pixels depuis toujours.
     80 et non plus 64 (#1079) : c'est ce qui laisse la marque grandir (cf. `.marque`), et tout ce
     qui se colle dessous lit cette variable. */
  --haut-barre: 80px;
  /* La HAUTEUR D'ÉCRAN que lisent les panneaux qui tiennent l'écran (#1748) : `100vh` ici, et au
     téléphone ce que le clavier laisse (`--haut-visible`, `js/hauteur-visible.js`). Une variable et
     non une règle de plus par panneau : les écrans du téléphone qui viendront s'y calent d'eux-mêmes. */
  --haut-ecran: 100vh;
  /* formes */
  --radius: 12px;
  --shadow: 0 4px 18px rgba(0, 0, 0, .35);
  /* l'ombre de ce qui FLOTTE au-dessus de la page sans voile — la fenêtre d'aperçu, le bandeau de
     sélection (#1325) : plus haute que celle d'une carte, puisque rien d'autre ne l'en détache */
  --ombre-flottante: 0 10px 34px rgba(0, 0, 0, .55);
}

/* ─────────── LE THÈME CLAIR (#859) ───────────
 *
 * Les MÊMES jetons, d'autres valeurs. Pas une seconde feuille, pas une règle de composant
 * dupliquée : la palette est déclarée en un point depuis #775, et c'est ce qui permet à un thème de
 * n'être qu'un bloc de variables. Une feuille « clair.css » aurait recopié toutes les règles qui
 * n'ont rien à voir avec la couleur, et c'est celle qu'on ne relit pas qui aurait divergé.
 *
 * Ce qui n'est PAS ici ne dépend pas du thème : la police du nom, la hauteur de la barre, le rayon.
 * `accent-color` non plus — il vaut `var(--accent)` sur `:root` et suit donc la redéfinition.
 *
 * Le sélecteur ne porte QUE l'attribut, sans `:root` devant : posé sur la racine par le front, il
 * vaut pour la page entière, et posé sur n'importe quel bloc il vaut pour ce bloc. C'est ce qui
 * permet à `devtools/maquette-identite.html` de montrer les DEUX palettes côte à côte, sans une
 * ligne de script et sans redéclarer une couleur — c'est-à-dire de rester ce qu'elle promet d'être.
 * Pas de `@media (prefers-color-scheme: light)` : ce thème est un CHOIX enregistré par la personne,
 * pas une déduction du système — sans quoi qui a choisi « sombre » le verrait basculer à l'heure où
 * son poste change d'avis, et le réglage aurait l'air de ne pas tenir.
 *
 * LES VALEURS SONT MESURÉES, pas choisies à l'œil (le plancher de WCAG AA, 4,5, celui de
 * `CONTRASTE_ACCENT_MIN` dans `app.js`), et sur le fond le MOINS FAVORABLE de la palette — ici le
 * plus profond (`--bg`), puisque les encres y sont sombres : txt 13,2 · txt2 9,4 · muted 5,9 ·
 * muted2 4,5 · accent 4,8 · marque 4,8 · ok 5,1 · warn 5,0 · bad 5,9 · info 5,3. C'est le niveau du
 * thème sombre mesuré sur SON fond le moins favorable (`--panel2`), et non un niveau inventé :
 * `devtools/verifier_front.cjs` le remesure des deux côtés au lieu de le croire.
 *
 * L'ordre des trois fonds garde son sens — `--bg` le plus profond, `--panel2` le plus proche —, ce
 * qui veut dire ici « du plus teinté au plus blanc ». C'est ce qui permet aux règles de profondeur
 * écrites une fois de continuer à dire la bonne chose, et c'est aussi ce dont `accentLisible`
 * dépend : le fond le moins favorable change de bout selon le thème, jamais de nom. */
[data-theme="clair"] {
  color-scheme: light;
  /* fonds, du plus profond au plus proche — des crèmes chauds, jamais un gris-bleu */
  --bg: #f1eade;
  --panel: #faf6ec;
  --panel2: #fffdf6;
  --line: #d5c8ad;
  /* les deux fonds des barres collées en haut : le dégradé va toujours du plus clair au plus
     soutenu, comme en sombre — c'est ce qui pose la barre au-dessus de la page */
  --barre-haut: #f7f1e3;
  --barre-bas: #e6dcc7;
  /* textes, du plus lisible au plus discret */
  --txt: #2b2118;
  --txt2: #463929;
  --muted: #665740;
  --muted2: #786949;
  /* accents et significations. L'accent est le MÊME or laiton, descendu en clarté : l'or de #948
     tient 8,5 de contraste sur un brun très sombre et 1,9 sur un crème — c'est la teinte qui dit
     « ce qu'on peut toucher », et c'est la clarté qui doit suivre le fond. */
  --accent: #875f14;
  --accent-soft: rgba(135, 95, 20, .13);
  /* ce qui s'écrit SUR un fond d'accent : l'accent est sombre ici, donc l'encre est claire — et
     plus claire que `--panel2`, ce qui GARANTIT le corollaire que `verifier_front` mesure (un
     accent lisible sur le fond le moins favorable l'est aussi sur cette encre-là) */
  --sur-accent: #ffffff;
  --marque: #8b5c06;
  --ok: #14722f;
  --warn: #a4490a;
  --bad: #ae1a1a;
  --info: #0d6b63;
  /* les encres des badges (#1096) : ≥ 4,8 sur les cinq fonds du thème, voile du badge compris —
     plus sombres que `--ok`/`--bad`/`--warn`, qui ne tiennent pas 4,5 sous un voile rose ou vert */
  --encre-ok: #115e27;
  --encre-bad: #931414;
  --encre-warn: #733600;
  --encre-transfert: #16567e;
  --encre-test: #6b21a8;
  /* les rôles d'une carte de Terminus (#1230) : ≥ 5,5 sur les cinq fonds du thème */
  --encre-pilote: #86198f;
  --encre-bilan: #5b21b6;
  --encre-epreuve: #115e59;
  --encre-saut: #3730a3;
  /* les types sans rôle, pour le robot d'une carte (#1979) : ≥ 5,9 sur les fonds du thème */
  --encre-type-feat: #166534;
  --encre-type-bug: #991b1b;
  --encre-type-mco: #6b4423;
  --encre-type-doc: #733600;
  --encre-type-spec: #3f6212;
  --encre-type-macro: #075985;
  --encre-type-echeance: #9f1239;
  --encre-type-autre: #5c5343;
  --encre-assistant: #155e75;
  /* le filet des éléments neutres qu'on veut CREUX et le fond de la P3 : clairs ici, et l'encre qui
     s'écrit dessus retourne au brun sombre */
  --neutre: #ded2b8;
  --sur-neutre: #3a2f21;
  /* L'ÉTOILE descend en un bleu ardoise : un blanc bleuté disparaît sur un crème, et le nom
     l'écrit — elle doit donc tenir le plancher de lisibilité sur `--bg` (5,7), comme l'accent. Le
     halo s'éteint. Le CIEL, lui, ne prend pas l'ardoise : à faible opacité sur le crème, un gris
     bleuté se lit comme une salissure (#1098). Un azur franc, un peu plus dosé qu'elle ne l'était,
     donne des traits d'un bleu net — un dessin à l'encre, pas de la poussière. */
  --etoile: #2e5f78;
  --lueur-etoile: transparent;
  --ciel: #1f5fbf;
  --decor: .28;
  /* le voile derrière une boîte de dialogue : un brun translucide, plus léger qu'en sombre — sur une
     page claire, un voile aussi dense qu'en nuit ferait disparaître ce qu'on garde en contexte */
  --voile: rgba(60, 46, 20, .40);
  /* L'ombre. Une ombre noire à 35 % lue sur un crème fait une salissure grise autour de chaque
     carte : la teinte va au brun de la palette, et l'opacité tombe de moitié. */
  --shadow: 0 4px 18px rgba(74, 58, 30, .16);
  --ombre-flottante: 0 10px 34px rgba(74, 58, 30, .26);
}

/* ─────────── LE THÈME CONTRASTE (#1579) ───────────
 *
 * Pour qui lit mal : les MÊMES jetons que le clair, aucun de plus, sur un fond NOIR, un texte
 * BLANC et un accent JAUNE — la paire la plus contrastée qui existe, et celle des modes « contraste
 * élevé » des systèmes. Fond noir plutôt que blanc : c'est aussi la base du thème par défaut, donc
 * tout ce que la feuille ne réserve qu'au clair (la marque sur fond clair, les captures d'aide en
 * clair) reste juste ici sans une règle de plus — la marque sur sombre et les captures sombres
 * s'appliquent d'elles-mêmes.
 *
 * LE NIVEAU EST AAA, et mesuré comme les deux autres : chaque encre tient 7 sur les cinq fonds
 * (`--bg`, `--panel`, `--panel2`, les deux bouts de la barre du haut), et encore 7 sous le voile à
 * 16 % d'un badge de sa couleur ; les bordures (`--line`) tiennent 6, le contour du focus (l'accent
 * à 65 %) 5,2. `devtools/verifier_front.cjs` le remesure au lieu de le croire, et c'est aussi lui
 * qui tient le plancher d'accent du front à 7 dans ce thème (`PALETTE_PAR_THEME`, dans `app.js`).
 *
 * Les fonds restent ORDONNÉS — `--bg` le plus profond, `--panel2` le plus proche —, mais à peine
 * écartés : une carte se détache ici par sa BORDURE, franche, plutôt que par un fond qui éloignerait
 * le texte du noir. L'accent étant clair, le fond le moins favorable est le plus clair (`--panel2`),
 * comme en sombre. Pas de lueur, pas d'ombre qui compte : sur du noir, elles ne se voient pas. */
[data-theme="contraste"] {
  color-scheme: dark;
  /* fonds, du plus profond au plus proche — des noirs neutres, sans teinte */
  --bg: #000000;
  --panel: #0b0b0b;
  --panel2: #161616;
  --line: #9a9a9a;
  /* les deux fonds des barres collées en haut : du plus clair au plus soutenu, comme ailleurs */
  --barre-haut: #1c1c1c;
  --barre-bas: #000000;
  /* textes, du plus lisible au plus discret — même le plus discret tient 10 */
  --txt: #ffffff;
  --txt2: #f2f2f2;
  --muted: #dadada;
  --muted2: #c8c8c8;
  /* accents et significations : un jaune franc, et des couleurs d'état éclaircies jusqu'à 7 */
  --accent: #ffc400;
  --accent-soft: rgba(255, 196, 0, .13);
  /* ce qui s'écrit SUR un fond d'accent : le jaune est clair, l'encre est noire (13,1) */
  --sur-accent: #000000;
  --marque: #ffcc33;
  --ok: #5ee88a;
  --warn: #ffb340;
  --bad: #ff8a8a;
  --info: #5fe0d0;
  /* les encres des badges (#1096) : ≥ 7 sous le voile du badge, sur les cinq fonds */
  --encre-ok: #9af5b6;
  --encre-bad: #ffb8b8;
  --encre-warn: #ffe08a;
  --encre-transfert: #9edcff;
  --encre-test: #e0c4ff;
  /* les rôles d'une carte de Terminus (#1230) */
  --encre-pilote: #f5bdff;
  --encre-bilan: #d4c8ff;
  --encre-epreuve: #7ff0dc;
  --encre-saut: #bcc6ff;
  /* les types sans rôle, pour le robot d'une carte (#1979) */
  --encre-type-feat: #9af5b6;
  --encre-type-bug: #ffb8b8;
  --encre-type-mco: #ebcdac;
  --encre-type-doc: #ffe08a;
  --encre-type-spec: #d4f78a;
  --encre-type-macro: #9edcff;
  --encre-type-echeance: #ffbcc6;
  --encre-type-autre: #e2dacb;
  --encre-assistant: #8ff1ff;
  /* le filet des éléments neutres qu'on veut CREUX et le fond de la P3, et son encre (11,0) */
  --neutre: #3c3c3c;
  --sur-neutre: #ffffff;
  /* L'ÉTOILE écrit le nom : blanc pur, sans halo. Le CIEL est moins dosé qu'en sombre — un fond
     d'étoiles trop présent est exactement ce qui gêne qui a choisi ce thème. */
  --etoile: #ffffff;
  --lueur-etoile: transparent;
  --ciel: #8fc4f2;
  --decor: .18;
  /* le voile derrière une boîte de dialogue : plus dense qu'en sombre, la boîte doit TRANCHER */
  --voile: rgba(0, 0, 0, .8);
  --shadow: 0 4px 18px rgba(0, 0, 0, .6);
  --ombre-flottante: 0 10px 34px rgba(0, 0, 0, .8);
}

/* Orbitron, la police du NOM (#918), choisie sur une planche de neuf polices rendues côte à côte —
   Michroma, celle du kit, faisait large et plat en en-tête. OFL, licence à côté du fichier ; servie
   par l'app et jamais par un CDN, c'est la règle du front. Le fichier est VARIABLE (graisses 500 à
   700) : une seule requête pour le nom. Elle ne sert qu'au nom — un écran de
   travail dense ne se lit pas dans une police d'affiche. `swap` : le nom s'affiche tout de suite
   dans la police de secours plutôt que de rester invisible le temps du chargement. */
@font-face {
  font-family: "Orbitron";
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url("../polices/orbitron-latin.woff2") format("woff2");
}

/* ─────────── socle ─────────── */

* {
  box-sizing: border-box;
}

/* `hidden` masque, quoi qu'en dise la mise en page. Le `!important` n'est pas une verrue : la
   feuille du NAVIGATEUR pose déjà `[hidden] { display: none }`, mais à spécificité égale c'est
   l'origine auteur qui l'emporte — et n'importe quelle règle d'auteur à `display` explicite annule
   alors l'attribut, sans rien lever. C'est ce qui affichait les DEUX formulaires de la porte en même
   temps (#798) : `.porte-bloc { display: flex }` battait le `hidden` posé par `connexion.js`.
   Posée ici une fois, elle vaut pour tout futur bloc à `display` explicite qu'on voudra masquer —
   l'alternative, une classe « .cache » que chaque nouveau bloc devrait penser à porter, rejoue le
   même défaut au premier oubli. */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
  margin: 0;
}

/* LE FOND (#1079) : un ciel discret — deux nébuleuses à peine teintées, l'une de l'étoile en haut
   à droite, l'autre de l'or en bas à gauche, sur le fond du thème. `fixed` : elles ne défilent pas
   avec le tableau, un ciel qui glisse sous les cartes attire l'œil là où il n'y a rien à lire. */
body {
  background:
    radial-gradient(ellipse 60% 45% at 88% 4%, color-mix(in srgb, var(--etoile) 8%, transparent), transparent 70%),
    radial-gradient(ellipse 55% 40% at 6% 98%, color-mix(in srgb, var(--marque) 6%, transparent), transparent 70%),
    var(--bg);
  background-attachment: fixed;
  color: var(--txt);
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.45;
}

/* …et le DESSIN par-dessus : étoiles, constellations reliées comme des synapses, pistes de
   circuit, une orbite (`decor/fond.svg`). C'est un MASQUE et non une image : le fichier ne porte
   que des opacités, et c'est `--ciel` qui peint — le même dessin sert donc les deux thèmes sans
   une couleur écrite en dur ni une seconde copie. Un calque fixe DERRIÈRE la page (`z-index: -1`,
   le `body` n'étant pas un contexte d'empilement) : il ne se voit que dans les interstices, les
   panneaux et les cartes gardant leur fond plein — c'est ce qui le rend subtil, et lisible en
   dessous. Aucune animation : un fond qui bouge sous un écran de travail est une distraction. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: var(--ciel);
  opacity: var(--decor);
  -webkit-mask: url("../decor/fond.svg") 0 0 / 960px 960px repeat;
  mask: url("../decor/fond.svg") 0 0 / 960px 960px repeat;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 650;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

button {
  font-family: inherit;
}

.mono {
  font-family: ui-monospace, Consolas, monospace;
}

/* Les barres de défilement dans le thème (#918). Fines, un trait de la couleur des bordures sur une
   gouttière transparente : elles se voient quand on les cherche et ne découpent pas les colonnes du
   tableau en bandes grises. `scrollbar-color` vaut pour les navigateurs du poste (Chromium 121+,
   Firefox) — pas de `::-webkit-scrollbar` en plus, que ces versions ignorent dès que la propriété
   standard est posée : deux écritures dont une morte. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

/* Les champs de saisie, par DÉFAUT (#918). `:where` ne pèse rien en spécificité, et c'est tout
   l'intérêt : ces lignes habillent les champs qu'aucune règle ne vise (la liste déroulante des
   Paramètres était restée blanche) sans jamais reprendre la main sur une règle écrite pour un champ
   précis, qui l'emporte toujours. Les cases et les boutons radio n'en font pas partie : ils prennent
   l'accent par `accent-color`, et une bordure posée dessus les déformerait. */
:where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea) {
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background-color: var(--panel2);
  color: var(--txt);
  font: inherit;
}

:where(input, select, textarea, button):focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 65%, transparent);
  outline-offset: 1px;
}

/* ─────────── barre du haut ─────────── */

.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  height: var(--haut-barre);
  padding: 0 22px;
  /* le filet AMBRE de la marque (#884) : la seule lumière ambre d'un écran de travail qui ne
     soit pas une alerte, et elle reste à la barre qui porte le nom */
  border-bottom: 1px solid color-mix(in srgb, var(--marque) 40%, var(--line));
  background: linear-gradient(180deg, var(--barre-haut), var(--barre-bas));
}

/* LA MARQUE (#884) : le « tesseract radiant » du kit RPrime — un hypercube projeté, ses arêtes
   ambre et son cube intérieur cyan, dans les anneaux du radiant. Elle a remplacé le point et les
   arcs de #775, et elle ne se teinte PLUS : le kit interdit d'en changer les couleurs, donc c'est un
   fichier (`marque/favicon.svg`), le même pour l'onglet, l'en-tête, la porte et l'écran d'accueil
   du téléphone — une seule écriture de la marque là où il y en avait quatre copies. */
.brand {
  display: flex;
  align-items: center;
  gap: 11px;
}

/* La variante TRANSPARENTE du kit (#918) : le tesseract et ses anneaux posés directement sur la
   barre. La tuile sombre de l'icône d'onglet s'y fondait et éteignait la marque ; le favicon, lui,
   garde sa tuile, qui est ce qui le détache d'une barre d'onglets claire.
   La taille est celle de la BARRE DU HAUT, seul écran qui lise cette règle sans la surcharger — la
   porte a `.marque-grande`.
   56 et non 44 (#964) : le dessin porte ~15 % de marge à l'intérieur de sa boîte (l'anneau extérieur
   est tracé à r=214 dans un carré de 512), donc la marque se lisait un tiers plus petite que son
   `width` ne le laissait croire. 72 et non plus 56 (#1079), demandé : la barre est donc montée à
   80 px (`--haut-barre`), qui ancre aussi les onglets et le panneau latéral — ils suivent d'eux-mêmes. */
.marque {
  display: block;
  width: 72px;
  height: 72px;
  flex: none;
}

/* LAQUELLE DES DEUX FORMES on montre (#859). La page porte les deux `<img>` et la feuille en cache
   une : un `src` ne se change pas en CSS, et le choix appartient au thème — c'est ici qu'il vit,
   pas dans `app.js`, qui aurait eu à repeindre l'en-tête à chaque bascule.
   `display: none` et non `visibility` : la barre est un `flex`, et une marque invisible garderait
   sa place de 56 px à côté de celle qu'on montre. Le `!important` de `[hidden]` ne s'applique pas —
   ce n'est pas l'attribut qui masque ici, c'est le thème. */
.marque-sur-clair {
  display: none;
}

[data-theme="clair"] .marque-sur-sombre {
  display: none;
}

[data-theme="clair"] .marque-sur-clair {
  display: block;
}

/* Sur la porte, la marque a la place de respirer : c'est la première page d'une installation, et la
   seule où le nom n'est pas en concurrence avec un écran de travail. */
.marque-grande {
  width: 112px;
  height: 112px;
  margin: 0 auto 6px;
}

/* Le NOM : « RPrime », en casse mixte comme on l'écrit (#918), et sans ornement à la suite (#1099 :
   le prime ambre qu'on y posait redoublait le « Prime » du nom). */
.brand h1,
.ouverture-nom,
.porte-marque h1 {
  color: var(--etoile);
  font-family: var(--police-marque);
  font-weight: 600;
  letter-spacing: .03em;
  /* le nom s'écrit dans le blanc bleuté des étoiles du logo (#1079) */
  text-shadow: 0 0 14px var(--lueur-etoile);
}

.brand h1 {
  font-size: 21px;
  line-height: 1.1;
}

.brand .sub {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: 12px;
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Vide tant que le bouton ▶ Agent n'est pas dessiné (#1056) : sans ça, il laisserait un trou de la
   largeur d'un espacement dans la barre de qui n'y a pas droit. */
.agent-libre:empty {
  display: none;
}

/* Les notifications (#928) : une cloche et son panneau, posé sous elle. */
.notifs {
  position: relative;
}

.notif-compte {
  font-weight: 600;
  color: var(--accent);
}

.notif-panneau {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 50;
  width: min(380px, 90vw);
  max-height: 60vh;
  overflow-y: auto;
  padding: 10px;
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 8px;
}

.notif-panneau[hidden] {
  display: none;
}

.notif-panneau ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.notif-entete {
  margin-bottom: 6px;
  color: var(--muted);
  font-size: 12px;
}

.notif-ligne {
  display: block;
  padding: 6px 4px;
  color: inherit;
  text-decoration: none;
  border-radius: 6px;
}

.notif-ligne:hover {
  background: var(--panel);
}

.notif-titre {
  display: block;
  font-weight: 600;
}

.notif-texte {
  display: block;
  color: var(--muted);
  font-size: 12px;
}

/* RPrime (#1264), même raison : muet dans la fenêtre du poste et hors mode développeur. Le bouton
   et son interrupteur « en fond » (#1268) restent collés l'un à l'autre. */
.ouvrir-rprime {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.ouvrir-rprime:empty {
  display: none;
}

/* L'installeur de l'AUTRE système (#1666), à côté de « Télécharger l'app » : un lien, pas un bouton. */
.ouvrir-rprime .autre-systeme {
  font-size: 12px;
  color: var(--muted);
}

/* « Remonter » dans la barre du haut (#1603) : au téléphone seulement, où il quitte la rangée des
   onglets qui défile (cf. le bloc « au téléphone »). Partout ailleurs, il est à droite des onglets. */
.remonter-haut {
  display: none;
  align-items: center;
}

/* LE CARNET (#1464) : sur la page HÉBERGÉE de la fenêtre d'un poste qui tient un carnet à lui, le
   bouton qui ramène au poste. La page du poste n'en a plus (#1815) : ce qui s'y écrit dit son carnet
   par son badge. */
.carnet {
  display: inline-flex;
  align-items: center;
}

.carnet:empty {
  display: none;
}

/* `--muted` et non `--muted2` (#1139) : sur le bas de la barre du haut, en clair, `--muted2` tombait
   à 3,9 — un horodatage de 11 px qu'on vient justement lire. */
.genere {
  color: var(--muted);
  font-size: 11px;
}

/* La pastille de santé : trois états, et le troisième est « on ne sait pas encore » — c'est celui
   qu'elle porte au chargement, avant la réponse de /api/health. */
.pastille {
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel2);
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
}

.pastille.ok {
  border-color: rgba(34, 197, 94, .40);
  background: rgba(34, 197, 94, .15);
  color: var(--encre-ok);
}

.pastille.ko {
  border-color: rgba(239, 68, 68, .45);
  background: rgba(239, 68, 68, .16);
  color: var(--encre-bad);
}

/* ─────────── dans la fenêtre de bureau (#1237) ───────────
   La fenêtre RPrime dessine sa propre barre de titre (`bureau/barre.js`), qui porte la marque et le
   nom : elle pose `fenetre-bureau` sur la racine et publie sa hauteur en `--barre-fenetre` (32 px ;
   0 partout ailleurs, et c'est ce que les `100vh` de cette feuille soustraient). Dans la fenêtre,
   et là seulement :
   - l'EN-TÊTE ne s'affiche plus — la marque serait dite deux fois l'une sous l'autre, et son seul
     geste, « Lancer l'app », n'a pas de sens dans l'app ; tout le reste est déjà dans les onglets
     des Paramètres (#1302), ici comme dans un navigateur ;
   - la page ne défile plus : c'est `main` qui défile, SOUS les onglets. La barre de défilement
     longe le contenu, et non la barre de titre ni les onglets. `main` prend alors toute la largeur
     pour que sa barre soit au bord de la fenêtre, et centre son contenu par ses marges.
   Dans un navigateur ou sur téléphone, rien de tout ça : il n'y a pas de barre de titre pour porter
   la marque, et l'en-tête reste. */
html.fenetre-bureau {
  --haut-barre: 0px;
  overflow: hidden;
}

html.fenetre-bureau body {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

html.fenetre-bureau .topbar {
  display: none;
}

html.fenetre-bureau .tabs {
  position: static;
  flex: none;
}

html.fenetre-bureau main {
  flex: 1;
  width: 100%;
  max-width: none;
  min-height: 0;
  overflow-y: auto;
  padding-inline: max(22px, calc((100% - 1396px) / 2));
}

/* ─────────── onglets ─────────── */

/* Les onglets se collent juste sous la barre du haut, à SA hauteur (`--haut-barre`) : c'est la même
   valeur des deux côtés, et non une mesure recopiée qui cesse d'être vraie au premier réglage. */
.tabs {
  position: sticky;
  top: var(--haut-barre);
  z-index: 25;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 10px 22px 0;
  /* À l'or de la MARQUE, et jamais à l'accent (#1044, qui défait ce point de #990) : la barre est
     celle de l'APP, pas d'un projet — teinte, elle peignait « Tickets » et « Paramètres » aux
     couleurs d'un projet qu'on ne regarde pas forcément. `--marque` est le seul or que la teinte ne
     touche jamais, comme pour le cadre d'une carte cochée. */
  border-bottom: 1px solid color-mix(in srgb, var(--marque) 45%, var(--line));
  background: var(--barre-bas);
}

.tabs button {
  padding: 9px 16px;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 8px 8px 0 0;
  background: none;
  color: var(--muted);
  font-size: 14px;
  cursor: pointer;
}

.tabs button:hover {
  color: var(--txt);
}

.tabs button.active {
  border-bottom-color: var(--marque);
  background: color-mix(in srgb, var(--marque) 13%, transparent);
  color: var(--txt);
}

/* À DROITE DES ONGLETS (#1474) : « ? Aide », et ce qui viendra la rejoindre. Le groupe prend la
   marge, pour que ses liens restent serrés entre eux au lieu que chacun se pousse au bord. */
.tabs-droite {
  display: flex;
  flex: none;
  gap: 4px;
  margin-left: auto;
}

/* La relève d'un suivi externe en retard (#1891) : la pastille de santé, centrée sur ses voisins. */
.releve-en-retard {
  align-self: center;
  max-width: 420px;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: help;
}

/* Un lien qui a l'allure d'un onglet : il s'allume comme eux quand on est sur sa vue. « Remonter »
   (#1472) en prend l'allure aussi, sans adresse : il ouvre une boîte, il ne mène nulle part. */
.tab-lien[role="button"] {
  cursor: pointer;
}

.tab-lien {
  padding: 9px 16px;
  border-bottom: 2px solid transparent;
  border-radius: 8px 8px 0 0;
  color: var(--muted);
  font-size: 14px;
}

.tab-lien:hover {
  color: var(--txt);
  text-decoration: none;
}

.tab-lien.active {
  border-bottom-color: var(--marque);
  background: color-mix(in srgb, var(--marque) 13%, transparent);
  color: var(--txt);
}

main {
  max-width: 1440px;
  margin: 0 auto;
  padding: 22px;
}

.vue {
  display: none;
}

.vue.active {
  display: block;
}

/* L'onglet Terminus (#1130) : la page de Terminus en iframe, sur toute la hauteur que la barre du
   haut et les onglets laissent. Les classes portent le préfixe `onglet-terminus-` : la page de
   Terminus a les siennes, et `verifier_terminus` refuse qu'une classe existe des deux côtés. */
.onglet-terminus-cadre {
  display: block;
  width: 100%;
  height: calc(var(--haut-ecran) - var(--barre-fenetre, 0px) - var(--haut-barre) - 110px);
  min-height: 420px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
}

/* Le cadre et, à sa droite, la colonne du plan d'un PILOTE (#1206) : la même colonne que la vue
   Tickets, à la hauteur du cadre, où seules les étapes défilent — la tête, la jauge et le pied
   restent sous les yeux. Sans pilote, la colonne est `hidden` et le cadre prend toute la largeur.
   Elle ne se replie pas (#1341) : c'est une colonne de lecture, sans geste. */
.onglet-terminus-corps {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}

.onglet-terminus-corps .onglet-terminus-cadre {
  flex: 1;
  min-width: 0;
}

.onglet-terminus-plan {
  display: flex;
  flex: none;
  flex-direction: column;
  width: 320px;
  height: calc(var(--haut-ecran) - var(--barre-fenetre, 0px) - var(--haut-barre) - 110px);
  min-height: 420px;
}

.onglet-terminus-plan[hidden] {
  display: none;
}

.onglet-terminus-plan .pl-rail {
  position: static;
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  max-height: none;
  overflow: hidden;
}

.onglet-terminus-plan .pl-rail > * {
  flex: none;
}

.onglet-terminus-plan .pl-rail > .pl-defile {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 3px;
}

.onglet-terminus-panne {
  max-width: 640px;
  margin: 40px auto;
  padding: 18px 22px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel2);
}

/* Un cadre par poste (#1247) : un seul se voit. Sans cette règle, `display: block` l'emporterait sur
   l'attribut `hidden`. */
.onglet-terminus-cadre[hidden] {
  display: none;
}

/* Les cartes EN ROUTE (#1729) : une demande de lancement qu'aucun terminal ne porte encore — en
   attente d'un poste, ou prise et en train de s'ouvrir. Grisées, avec une roue ; une bande au-dessus
   du cadre, qui passe à la ligne, et disparaît quand la vraie carte arrive. Depuis #1769, c'est le
   REPLI : une page de Terminus en version 2 les range dans sa colonne des sessions. */
.onglet-terminus-en-route {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}

.onglet-terminus-en-route[hidden] {
  display: none;
}

.onglet-terminus-en-route-carte {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 420px;
  padding: 8px 12px;
  border: 1px dashed var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.onglet-terminus-en-route-carte:hover {
  border-color: var(--accent);
}

.onglet-terminus-en-route-texte {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.onglet-terminus-en-route-texte b {
  color: var(--txt);
}

.onglet-terminus-en-route-roue {
  flex: none;
  width: 18px;
  height: 18px;
  border: 2px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  /* Quelques tours en arrivant, puis immobile (#2109) : une roue qui tourne sans fin fait dessiner la
     fenêtre RPrime à chaque image, et la roue dessinée dit « en route » à elle seule. */
  animation: onglet-terminus-tourne 1s linear 2;
}

/* En file d'attente, la roue tourne lentement : rien ne s'ouvre encore. */
.onglet-terminus-en-route-carte.en-file .onglet-terminus-en-route-roue {
  animation-duration: 3s;
}

/* ARRÊTÉE (#1915) : départ manqué, ou poste qui ne salue pas. La roue ne tourne plus — elle
   promettait une suite qui ne viendra pas sans un geste. */
.onglet-terminus-en-route-carte.arretee {
  border-color: var(--warn);
  color: var(--txt2);
}

.onglet-terminus-en-route-carte.arretee .onglet-terminus-en-route-roue {
  border-color: var(--warn);
  animation: none;
}

/* LA CROIX d'une demande en file (#1920) : collée à sa carte, dans la même bande — un bouton à part
   et non un bouton dans le bouton, que le HTML interdit. Elle ouvre la confirmation, ne retire rien. */
.onglet-terminus-en-route-retirer,
.t-retirer {
  flex: none;
  border: 1px dashed var(--line);
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.onglet-terminus-en-route-retirer {
  align-self: stretch;
  margin-left: -6px;
  padding: 0 10px;
  border-radius: 10px;
}

/* Sur une carte du tableau, à la suite de la pastille de session : même hauteur qu'elle. */
.t-retirer {
  padding: 1px 5px;
  border-radius: 999px;
  font-size: 10px;
}

.onglet-terminus-en-route-retirer:hover,
.t-retirer:hover {
  border-color: var(--warn);
  color: var(--txt);
}

/* La boîte des refus de lancement (#1915) : une ligne par ticket resté, la phrase du serveur à la
   taille du texte — c'est elle qu'on vient lire —, et son geste dessous. */
.refus-lancement {
  margin-top: 12px;
  line-height: 1.45;
}

.refus-lancement p {
  margin: 0 0 8px;
  color: var(--txt);
}

@keyframes onglet-terminus-tourne { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .onglet-terminus-en-route-roue { animation: none; }
}

/* La barre des POSTES (#1247) : celui de la fenêtre et chaque poste relayé, dès qu'il y en a deux —
   le cadre montré est celui du bouton actif. Elle passe à la ligne plutôt que de déborder à 360 px. */
.onglet-terminus-postes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* La tête de l'onglet Terminus (#1294) : la barre des postes, et « Historique » poussé à droite —
   toujours là, même avec un seul poste, puisque c'est d'ici qu'on voit les cartes partir. */
.onglet-terminus-tete {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-bottom: 8px;
}

.onglet-terminus-postes[hidden] {
  display: none;
}

/* Les deux limites d'un poste, sur la ligne de son réglage ; elles passent à la ligne à 360 px. */
.limites-poste {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 6px;
}

.limite-champ {
  display: flex;
  flex-direction: column;
  font-size: 0.85em;
}

.limite-champ input {
  width: 9em;
}

.onglet-terminus-postes .btn-sm.actif {
  border-color: var(--accent);
  color: var(--accent);
}

/* Le badge de l'onglet Terminus (#1133) : combien de sessions attendent quelqu'un. Et celui de
   Personnes (#1721) : combien d'invitations de projet attendent l'admin de l'organisation. Et celui
   de Paramètres (#1994) : combien d'étapes de la mise en place restent dues. */
.onglet-attentes,
.onglet-invitations,
.onglet-mise-en-place {
  display: inline-block;
  min-width: 18px;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--marque);
  color: var(--bg);
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
}

/* La marque discrète d'une carte du tableau dont la session a sa carte dans Terminus (#1133). */
.t-terminus {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
}

/* La session ouverte sur le ticket, quel que soit son poste (#1647) : même forme que la marque de
   Terminus, la pastille verte quand elle est lancée. */
.t-session {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.t-session.lancee {
  border-color: var(--ok);
  color: var(--encre-ok);
}

/* La marque et un badge d'état mènent à la session d'un clic (#1209) : ils le disent au survol. */
.t-terminus[data-en-terminus],
.compteur.vers-terminus {
  cursor: pointer;
}

.t-terminus[data-en-terminus]:hover,
.compteur.vers-terminus:hover {
  border-color: var(--accent);
}

.section-title {
  margin: 0 0 12px;
  color: var(--muted);
  font-size: 13px;
  letter-spacing: .6px;
  text-transform: uppercase;
}

/* ─────────── pastilles, étiquettes, badges ─────────── */

.dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-right: 6px;
  border-radius: 50%;
  vertical-align: middle;
}

.tag {
  display: inline-block;
  margin: 2px 3px 0 0;
  padding: 1px 7px;
  border-radius: 6px;
  background: var(--line);
  color: var(--txt2);
  font-size: 11px;
}

/* Un skill dont l'app sait dire à quoi il sert (#966) : le survol le décrit, le clic en ouvre le
   texte. Le curseur est tout ce qui l'annonce — le souligné ferait un lien, et une pastille n'en est
   pas un ; la teinte d'accent en ferait un bouton, alors qu'il y en a six sur une carte. */
.tag.lisible {
  cursor: pointer;
}

.tag.lisible:hover {
  color: var(--txt);
  background: var(--panel2);
}

/* Un skill que le projet attend et que l'app ne sait pas servir. Creux plutôt que coloré : ce n'est
   pas une alerte — il vient sans doute du dépôt du projet —, c'est une promesse qu'on ne fait pas. */
.tag.hors-catalogue {
  background: transparent;
  border: 1px dashed var(--neutre);
  color: var(--muted2);
}

/* Les trois priorités. Couleurs en dur et assumé : ce sont des feux, pas des nuances de thème —
   P1 rouge, P2 orange, P3 gris. Le rouge est celui où le blanc tient 4,5 (#1137) : le précédent,
   plus vif, ne le tenait qu'à 3,9, et c'est une encre qui ne dépend d'aucun thème. */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}

.badge.p1 {
  background: #d1343a;
  color: #fff;
}

.badge.p2 {
  background: #f5a623;
  color: #2a1c00;
}

.badge.p3 {
  background: var(--neutre);
  color: var(--sur-neutre);
}

/* Une couleur par état, et la même partout : compteurs d'un projet, titres de colonnes. Deux
   couleurs pour un même état seraient deux vocabulaires à apprendre. */
.st-todo {
  color: var(--muted);
}

.st-in_progress {
  color: var(--info);
}

.st-blocked {
  color: var(--bad);
}

.st-test {
  color: var(--encre-test);
}

.st-done {
  color: var(--ok);
}

/* Badges d'une carte de ticket. Le type et le projet reçoivent leur couleur depuis le JS (elle est
   dans les données) : ici, seulement la forme. */
.type-badge,
.proj-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 7px;
  border: 1px solid;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

.proj-chip {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.effort-badge {
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--muted);
  font-size: 11px;
}

.dep-badge {
  padding: 1px 6px;
  border: 1px solid rgba(239, 68, 68, .45);
  border-radius: 6px;
  color: var(--encre-bad);
  font-size: 11px;
}

/* Ce qu'un ticket bloque n'est pas un problème pour LUI : orange, pas rouge. */
.dep-badge.blk {
  border-color: rgba(245, 158, 11, .45);
  color: var(--encre-warn);
}

/* Un passage de main (#856) n'est pas une alarme : quelqu'un a la charge du ticket, et il va
   répondre. Bleu, donc, et pas le rouge d'une dépendance ouverte — la carte porte déjà le rouge du
   blocage, et deux rouges côte à côte ne se distinguent plus. */
.dep-badge.tr {
  border-color: rgba(56, 189, 248, .45);
  color: var(--encre-transfert);
}

/* « Ça se lit comme terminé, et ça ne l'est pas » (#816). Rempli, et pas seulement bordé comme les
   autres badges : celui-ci doit sauter aux yeux sur une carte rangée dans « En test » ou « Fait »,
   c'est-à-dire exactement là où le regard ne s'arrête plus. */
.alerte-badge {
  padding: 1px 6px;
  border: 1px solid var(--bad);
  border-radius: 6px;
  background: rgba(239, 68, 68, .16);
  color: var(--encre-bad);
  font-size: 11px;
  font-weight: 600;
}

.pas-livre {
  color: var(--encre-bad);
  font-weight: 600;
}

/* Une échéance change de ton en approchant : c'est le seul endroit du front où la couleur porte une
   information que le texte ne donne pas d'un coup d'œil. */
.due {
  color: var(--encre-warn);
  font-size: 11px;
}

.due.soon {
  color: var(--encre-bad);
  font-weight: 600;
}

.due.ok {
  color: var(--muted);
}

/* ─────────── barres d'outils ─────────── */

.filtres {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 16px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel2);
  color: var(--txt2);
  font-size: 12px;
  cursor: pointer;
}

/* La pastille active reçoit en plus, depuis le JS, la couleur du projet qu'elle désigne : on voit
   quel filtre est posé sans relire son libellé. */
.chip.active {
  border-color: var(--accent);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--panel2);
  color: var(--accent);
}

/* Le contour suit l'accent de l'écran (#990), comme le filet des onglets : c'est le champ qu'on
   regarde en premier, et il restait le seul élément neutre d'un écran teint aux couleurs du projet. */
.recherche {
  min-width: 210px;
  padding: 6px 11px;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-size: 13px;
}

/* Le sélecteur de tri de la barre (#923). Il n'a que sa TAILLE à reprendre : l'habillage des champs
   vient déjà du défaut posé en #918 (`:where(select)`), et le redire ici en ferait une deuxième
   écriture du même dessin. Sans ces deux lignes, il arrive à la taille des formulaires et fait une
   bosse au milieu de pastilles de 12 px. */
.tri,
.liste-filtre {
  padding: 4px 8px;
  font-size: 12px;
  cursor: pointer;
}

/* Les listes Types / Personnes / rôle (#926) prennent la même taille que le tri. Le rôle grisé dit
   qu'il n'agit pas tant qu'aucune personne n'est choisie. */
.liste-filtre:disabled {
  opacity: 0.5;
  cursor: default;
}

.btn-sm {
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel2);
  color: var(--txt2);
  font-size: 12px;
  cursor: pointer;
}

.btn-sm:hover {
  border-color: var(--accent);
}

/* Le bouton qui tient un panneau ouvert le DIT : sinon, replier ne se distingue pas de déplier, et
   on clique deux fois pour savoir où l'on en est.
   LE VOILE D'ACCENT SE POSE SUR `--panel2` (#1139), ici et partout où l'accent s'écrit dessus : il
   est translucide, et posé nu il laissait voir ce qui est dessous — en clair, l'accent tombait à
   4,05 sur `--bg` et à 3,6 sur la barre du haut, quand il tient 4,7 sur `--panel2`. C'est
   l'écriture des cartes du tableau (un dégradé plat par-dessus un fond plein). */
.btn-sm.actif {
  border-color: var(--accent);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--panel2);
  color: var(--accent);
}

/* ─────────── la recherche avancée (#735) ─────────── */

/* Le panneau des filtres, sous la barre. Il est RETIRÉ du DOM quand il est replié plutôt que caché
   en CSS : à l'inverse d'un texte de frise, ce qu'il contient n'a aucun intérêt pour la recherche du
   navigateur, et des dizaines de pastilles invisibles se retrouveraient dans un Ctrl+F. Son hôte
   (`#avance`) ne porte donc aucun style : replié, il ne reste qu'une div vide, qui ne creuse pas de
   trou sous la barre d'outils. */
.avance {
  display: grid;
  gap: 8px;
  margin-bottom: 16px;
}

.facette {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel2);
}

.facette-nom {
  min-width: 108px;
  color: var(--muted);
  font-size: 12px;
}

.facette-choix {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Un champ que le serveur cherche par défaut : coché sans être choisi. Pointillé et non plein — le
   plein dit « vous l'avez demandé », et les deux ne se défont pas de la même façon. */
.chip.defaut {
  border-style: dashed;
  border-color: var(--accent);
  color: var(--accent);
}

.facette-pied {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* POURQUOI une carte est sortie : l'extrait de commentaire qui a mordu. Tronqué par le serveur, pas
   par le CSS — ce qui est coupé ici se retrouverait quand même dans la recherche du navigateur, et
   un extrait de six lignes rendrait la carte illisible. */
.t-extraits {
  margin-top: 7px;
  display: grid;
  gap: 4px;
}

.extrait {
  padding: 5px 8px;
  border-left: 2px solid var(--accent);
  border-radius: 0 6px 6px 0;
  background: var(--accent-soft);
  color: var(--txt2);
  font-size: 11.5px;
  line-height: 1.45;
}

.extrait .ou {
  margin-right: 5px;
  color: var(--accent);
  font-weight: 600;
}

.extrait.de-plus {
  border-left-color: var(--line);
  background: transparent;
  color: var(--muted2);
}

.interrupteur {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
}

/* `margin-left: auto` pousse l'indication tout à droite de sa barre, quoi qu'il y ait avant. */
.hint {
  margin-left: auto;
  color: var(--muted2);
  font-size: 12px;
}

/* Une consigne ne vaut que pour SON pointeur (#1602) : au doigt, ni le clic droit ni le glisser
   n'existent, et les annoncer promettait un geste qui ne répond pas. Le doigt a sa phrase à lui ;
   l'échange se fait dans le bloc du doigt, plus bas — un seul bloc `(pointer: coarse)` par feuille. */
.hint.au-doigt {
  display: none;
}

/* Les filtres de la vue Tickets repliés derrière « Filtres » sur un téléphone (#1602) : ils y
   prenaient tout le premier écran, et le tableau commençait au troisième. Restent la recherche,
   « Nouveau ticket… » et son brouillon (`.hors-repli`), et depuis #1750 la barre des projets et
   le tri. L'état se lit sur le bouton lui-même (`aria-expanded`) : un seul endroit le tient.
   Au-dessus de 600 px, le bouton n'existe pas à l'écran et la barre reste telle qu'elle était :
   le pli (`.pli-filtres`) et l'hôte du tri n'y font pas de boîte, leurs enfants sont ceux de la
   barre comme avant eux. Leur dessin au téléphone est dans le bloc de fin de feuille. */
.bascule-filtres,
.tri-icone {
  display: none;
}

.pli-filtres,
.tri-hote {
  display: contents;
}

@media (max-width: 600px) {
  .bascule-filtres {
    display: inline-flex;
  }

  #vue-tickets:has(#bascule-filtres[aria-expanded="false"]) #avance,
  .barre-outils:has(#bascule-filtres[aria-expanded="false"]) > :not(.hors-repli, .pli-filtres),
  .barre-outils:has(#bascule-filtres[aria-expanded="false"]) .pli-filtres > :not(.tri-hote) {
    display: none;
  }

  /* Replié, le pli ne fait pas de boîte : seul le tri en sort, à sa place à côté de la recherche. */
  .barre-outils:has(#bascule-filtres[aria-expanded="false"]) .pli-filtres {
    display: contents;
  }
}

/* ─────────── vue Projets ─────────── */

/* `auto-fill` avec un minimum : les cartes se réorganisent seules de une à quatre colonnes, sans
   point de rupture à maintenir. Le minimum est plafonné à la largeur disponible (#1188) : à 360 px,
   345 px dépassaient l'écran moins ses marges, et la seule colonne sortait à droite. */
.grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(345px, 100%), 1fr));
  gap: 16px;
}

/* Une ligne par organisation (#1771), dessinée comme une colonne du tableau, mais couchée : le même
   cadre, la même tête en petites capitales. L'autre carnet y est une ligne REPLIÉE — sa tête seule. */
.lignes-projets {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.ligne-org {
  min-width: 0;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.ligne-org-tete {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 12px;
  color: var(--muted);
  font-size: 12px;
  letter-spacing: .5px;
}

.ligne-org-tete .ligne-org-nom {
  color: var(--txt2);
  text-transform: uppercase;
}

.ligne-org-tete .n {
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--panel2);
  color: var(--txt2);
}

.ligne-org-tete [data-montrer],
.ligne-org-tete [data-ouvrir-carnet] {
  margin-left: auto;
  letter-spacing: normal;
}

.ligne-org.repliee .ligne-org-tete {
  margin-bottom: 0;
}

.ligne-org-vide {
  margin: 0;
}

/* La barre des organisations (#1803), au-dessus de celle des projets : plus serrée qu'elle. */
.barre-organisations {
  margin-bottom: 8px;
}

/* D'où vient une organisation, un projet ou une carte quand les deux carnets partagent l'écran
   (#1803) : une ICÔNE (#1833), le mot en info-bulle — le carnet hébergé teinté de l'accent. */
.badge-carnet {
  display: inline-flex;
  flex: none;
  align-items: center;
  margin-left: 6px;
  color: var(--muted);
  vertical-align: middle;
  cursor: help;
}

.badge-carnet .icone {
  width: 14px;
  height: 14px;
}

.badge-carnet-heberge {
  color: var(--accent);
}

/* Les projets d'une AUTRE organisation : nom, clé, description, et le clic qui y fait passer. */
.grille-courte {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr));
  gap: 10px;
}

.carte-courte {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: center;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel2);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.carte-courte:hover {
  border-color: var(--accent);
}

.carte-courte-nom {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.carte-courte-desc {
  flex-basis: 100%;
  font-size: 12px;
  overflow-wrap: anywhere;
}

.carte {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
}

/* Le bandeau de couleur du projet, en haut de sa carte : c'est lui qu'on suit pour retrouver un
   projet dans la grille. Sa couleur vient du JS. */
.carte .bande {
  height: 4px;
}

.carte .corps {
  flex: 1;
  padding: 15px 16px;
}

.carte .tete {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

/* Le titre est cliquable — il ouvre la vue Tickets filtrée sur ce projet. Le curseur et le
   soulignement au survol sont là pour que ça se devine sans l'essayer. */
.carte h3 {
  font-size: 15px;
  cursor: pointer;
}

.carte h3:hover {
  color: var(--accent);
  text-decoration: underline;
}

.carte .etat {
  margin-top: 2px;
  color: var(--muted);
  font-size: 12px;
}

.carte .desc {
  margin: 10px 0;
  color: var(--txt2);
  font-size: 12.5px;
}

.compteurs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0;
}

.compteur {
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  font-size: 12px;
}

.compteur b {
  font-size: 14px;
}

.barre {
  overflow: hidden;
  height: 5px;
  margin-top: 6px;
  border-radius: 999px;
  background: var(--panel2);
}

.barre span {
  display: block;
  height: 100%;
}

.liens {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

.lien {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel2);
  color: var(--txt);
  font-size: 12.5px;
}

/* `flex-wrap` n'est pas cosmétique ici, c'est la seule chose qui rende les gestes atteignables
   (#890). Ce pied est le seul conteneur de la feuille que le JS remplit APRÈS coup, et d'un nombre
   variable de boutons — jusqu'à quatre, selon ce que le serveur a permis. Sur une piste de grille à
   345 px il reste ~313 px, quand les quatre en font près de 390 : sans repli, les items se
   compriment jusqu'à leur `min-content`, le texte se replie mot à mot, et « Supprimer… », qui est un
   mot insécable, dépasse — puis disparaît, parce que `.carte` porte `overflow: hidden`. Rien ne
   lève : le bouton existe et répond, il est simplement hors de la carte. */
.pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 16px;
  border-top: 1px solid var(--line);
  background: var(--panel2);
  color: var(--muted);
  font-size: 12px;
}

/* Le dossier d'un projet et ses deux gestes (#929), sur la carte comme sur la ligne : le chemin
   se coupe en « … » plutôt que de repousser 📁 et « Copier » hors de vue. */
.projet-dossier {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  max-width: 100%;
}

.projet-dossier .mono {
  overflow: hidden;
  min-width: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* La bascule Cartes / Liste : deux boutons collés, dont l'actif se lit par `.btn-sm.actif`. */
.bascule-vue {
  display: inline-flex;
  gap: 2px;
}

/* L'interrupteur DEV | Test des projets jumelés (#1795), un seul pour toutes les paires (#1986) :
   deux boutons, chacun allumé ou non — l'une, l'autre ou les deux faces, jamais aucune (la règle
   est dans `facesApresBascule`). Au bout de la barre des projets et dans celle de la vue Projets :
   même dessin. */
.faces {
  display: inline-flex;
  margin-left: 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
  vertical-align: middle;
}

.faces > button {
  padding: 1px 6px;
  border: 0;
  background: var(--panel);
  color: var(--muted2);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .04em;
  cursor: pointer;
}

.faces > button + button {
  border-left: 1px solid var(--line);
}

.faces > button.actif {
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--panel2);
  color: var(--accent);
}

/* La face d'un ticket sur sa carte, à côté de son projet : on sait d'où il vient quand les deux faces
   sont montrées ensemble (#1795). */
.face-du-ticket {
  padding: 0 5px;
  border: 1px solid var(--line);
  border-radius: 5px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .04em;
}

/* La vue Liste des projets (#929). Même construction que l'annuaire, pour la même raison (#891) :
   les pistes sont déclarées sur la LISTE, et chaque ligne les reprend par `subgrid` — une ligne qui
   porterait sa propre grille ne s'alignerait sur aucune autre. */
.liste-projets {
  display: grid;
  grid-template-columns: minmax(180px, 2fr) minmax(90px, .8fr) minmax(130px, 1fr)
    minmax(120px, 1fr) minmax(0, 2.4fr);
  column-gap: 12px;
  row-gap: 2px;
}

.lp-ligne {
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  align-items: center;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: var(--txt2);
  font-size: 13px;
}

.lp-ligne > * {
  min-width: 0;
}

.lp-tete {
  border-color: transparent;
  background: none;
  color: var(--muted2);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .06em;
}

/* Le nom se clique, comme le titre d'une carte : il ouvre le tableau de ce seul projet. */
.lp-nom {
  color: var(--txt);
  font-weight: 600;
  cursor: pointer;
}

.lp-nom:hover {
  color: var(--accent);
  text-decoration: underline;
}

.lp-avance {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
}

.lp-avance .barre {
  flex: 1;
  margin-top: 0;
}

/* Au téléphone (#1604), la ligne devient une carte : les pistes à minimum fixe de la liste ne
   descendent pas sous ~600 px, et c'est la PAGE entière qui glissait de côté, en-tête compris. Le
   nom prend toute la largeur, le reste passe dessous ; l'en-tête n'étiquette plus aucune colonne. */
@media (max-width: 600px) {
  .liste-projets {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .lp-ligne {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 12px;
  }

  .lp-ligne > * {
    overflow-wrap: anywhere;
  }

  .lp-tete {
    display: none;
  }

  .lp-nom,
  .lp-avance,
  .lp-ligne > .projet-dossier {
    flex-basis: 100%;
  }
}

/* ─────────── vue Tickets : le tableau en colonnes ─────────── */

/* Le NOMBRE de colonnes est posé par le JS (`--pistes`) : il dépend de ce qu'on affiche, et
   « Masquer les faits » en retire une. La GRILLE, elle, s'écrit ici (#1188) : posée en ligne, elle
   l'emportait sur toute règle de la feuille, et le téléphone ne pouvait pas en changer. */
.kanban {
  display: grid;
  grid-template-columns: repeat(var(--pistes, 1), 1fr);
  align-items: stretch;
  gap: 14px;
}

/* `min-width: 0` n'est pas cosmétique. Une piste de grille vaut « auto » par défaut, donc elle
   s'élargit jusqu'au contenu le plus long qu'elle porte — un titre sans espace suffit. Le tableau
   débordait alors de la page vers la droite, et la dernière colonne devenait invisible. */
.colonne {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 220px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  transition: background .12s, border-color .12s;
}

.tcarte,
.colonne h4 {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* La colonne visée pendant un glisser : le retour visuel qui dit où la carte va tomber. */
.colonne.depot {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* Bloqué se voit de loin : c'est la colonne dont le contenu appelle une action. */
.colonne[data-etat="blocked"] {
  border-color: rgba(239, 68, 68, .24);
  background: linear-gradient(180deg, rgba(239, 68, 68, .07), transparent 40%), var(--panel);
}

.colonne h4 {
  display: flex;
  gap: 6px;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  color: var(--muted);
  font-size: 12px;
  letter-spacing: .5px;
  text-transform: uppercase;
}

.colonne h4 .n {
  margin-left: auto;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--panel2);
  color: var(--txt2);
}

/* La case « tout cocher » d'une colonne (#922). Même dessin que la coche d'une carte — c'est le
   même geste, une case de plus haut —, mais alignée sur l'en-tête : l'interlettrage du titre
   écarterait le glyphe de ce qui le suit, et la casse ne le concerne pas. */
.colonne h4 .col-choix {
  padding: 0 2px;
  border: 0;
  background: none;
  color: var(--muted2);
  font-size: 14px;
  line-height: 1;
  letter-spacing: normal;
  cursor: pointer;
}

/* Cochée ET mixte prennent l'accent : les distinguer par la couleur ferait porter au TON une
   information que le glyphe dit déjà, et une teinte intermédiaire se lirait comme un bouton inerte. */
.colonne h4 .col-choix[aria-checked="true"],
.colonne h4 .col-choix[aria-checked="mixed"] {
  color: var(--accent);
}

/* Chaque colonne défile pour elle-même : une colonne chargée ne fait pas défiler les quatre autres
   pour rien. Le plafond laisse la barre du haut et les outils visibles — sous 1100 px seulement :
   au-delà, la vue tient dans l'écran et c'est la mise en page qui mesure (#989). */
.colonne-corps {
  flex: 1;
  overflow-y: auto;
  max-height: calc(var(--haut-ecran) - var(--barre-fenetre, 0px) - 300px);
  padding-right: 3px;
}

.colonne-vide {
  padding: 18px 8px;
  color: var(--muted2);
  font-size: 12px;
  text-align: center;
}

/* ─────────── la carte d'un ticket ─────────── */

/* Deux couleurs, et elles ne disent pas la même chose : le liseré de gauche donne le TYPE, le fond
   translucide donne le PROJET. Les deux viennent du JS. `cursor: grab` annonce qu'elle se glisse. */
.tcarte {
  position: relative;
  margin-bottom: 9px;
  padding: 10px 11px;
  border: 1px solid var(--line);
  border-left: 6px solid var(--accent);
  border-radius: 9px;
  background: var(--panel2);
  cursor: grab;
}

.tcarte:hover {
  border-color: var(--accent);
}

.tcarte.glisse {
  opacity: .5;
}

/* La carte « + » d'« À faire » (#920). Elle REPREND `.tcarte` et n'en réécrit pas la forme : seuls
   le trait, le fond et le curseur changent. Pointillés et fond vide parce que c'est une place à
   remplir et non une carte qui porte déjà quelque chose ; le liseré de type disparaît avec le reste
   du trait, elle n'a pas de type. `font: inherit` avant `font-size` : un bouton ne prend pas la
   police de la page tout seul, et la remettre après l'aurait écrasée. */
.tcarte-ajout {
  display: block;
  width: 100%;
  border: 1px dashed var(--line);
  background: none;
  color: var(--muted2);
  font: inherit;
  font-size: 13px;
  text-align: center;
  cursor: pointer;
}

.tcarte-ajout:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* Le numéro et la priorité sur une ligne à eux, le titre SOUS elle (#779) : partagée, la ligne
   laissait au titre la moitié de la carte, et trois mots suffisaient à le mettre sur trois lignes. */
.tcarte .t-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.tcarte .t-titre {
  margin-top: 4px;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.35;
}

/* Le numéro et les références d'une carte s'écrivent en `--muted` (#1139) : la couleur du projet
   ÉCLAIRCIT la carte en sombre, et `--muted2` y tombait sous 4 à 11 px. */
.tnum {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}

.tcarte .t-projet {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
  font-size: 11px;
}

/* 📁 puis ▶ ▶▶ au bout de la ligne du projet (#933, #925). Le groupe est poussé à droite d'un seul
   bloc : une marge automatique sur CHACUN les aurait écartés l'un de l'autre, en deux familles que
   rien ne sépare. */
.tcarte .t-outils {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}

/* ▶ et ▶▶ au bout de la ligne du projet (#933). Petits et neutres au repos : ils sont sur CHAQUE
   carte, et un tableau de trente cartes ne doit pas devenir un mur de boutons. L'accent au survol.
   Non rattachés à la carte : la tête de la colonne d'un plan porte les mêmes (#952), et une seconde
   règle pour le même bouton finirait par ne plus lui donner la même taille. */
.t-lancer {
  display: flex;
  gap: 4px;
  margin-left: auto;
}

/* 📁 a exactement la même case que ▶ : deux règles pour deux boutons voisins finiraient par ne plus
   leur donner la même taille, et la ligne du projet se mettrait à sautiller d'une carte à l'autre. */
.t-lance,
.t-dossier {
  display: grid;
  place-items: center;
  width: 24px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
  color: var(--txt2);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
}

/* Le double triangle d'« Autonome » se resserre pour tenir dans la même case que « Supervisé ». */
.t-lance[data-lancer="auto"] {
  letter-spacing: -.3em;
  text-indent: -.3em;
}

.t-lance:hover,
.t-dossier:hover {
  border-color: var(--accent);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--panel2);
  color: var(--accent);
}

/* Le dossier de travail sur la fiche (#925) : le chemin, puis son bouton. En ligne, parce que le
   chemin est long et que le bouton doit rester collé à CE chemin — la sortie de secours en porte un
   autre, juste en dessous. */
.fiche-dossier {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.fiche-dossier + .fiche-dossier {
  margin-top: 4px;
}

.tcarte .t-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 7px;
}

.tcarte .t-refs {
  margin-top: 6px;
  color: var(--muted);
  font-size: 11px;
}

.qui {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 7px;
  color: var(--muted);
  font-size: 11px;
}

/* Les initiales, sur une couleur calculée depuis l'identifiant de la personne : la même personne
   garde la même couleur d'un écran à l'autre, sans qu'on ait à en stocker une. Sa taille est FIXE :
   dans une ligne qui manque de place, c'est le nom à côté qui cède, jamais la pastille (#1538). */
.avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  color: #fff;
  font-size: 9.5px;
  font-weight: 700;
}

/* Les collaborateurs sur une carte (#943), à la suite du responsable. Des pastilles qui se
   CHEVAUCHENT légèrement : c'est ce qui les lit comme un groupe et non comme une liste, et c'est
   aussi ce qui garde la ligne courte quand ils sont trois. Le liseré reprend le fond de la carte
   — sans lui, deux pastilles voisines de teintes proches se fondent en une seule forme. */
.t-collabs {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
}

.t-collabs .avatar {
  width: 18px;
  height: 18px;
  box-shadow: 0 0 0 1.5px var(--panel2);
}

.t-collabs .avatar + .avatar {
  margin-left: -6px;
}

/* Le « +N » ne chevauche pas : c'est un NOMBRE, pas une personne de plus. */
.t-collabs-plus {
  margin-left: 4px;
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
}

/* ─────────── les gestes du tableau (#854) ───────────
 *
 * Trois choses nouvelles à l'écran, et une règle commune : rien de tout ça ne doit occuper de place
 * quand on ne s'en sert pas. La file de tri prend une piste dès qu'un ticket attend d'être pris et
 * la rend quand il n'y en a plus (#918, #921) ; le bandeau n'existe qu'avec une sélection ; la zone
 * de clôture n'apparaît qu'avec un glisser.
 */

/* La file « Non attribué ». Elle ressemble aux autres colonnes et se distingue d'un coup d'œil : ce
   n'est pas un état, c'est une question — le liseré ambre dit « ça attend quelqu'un », comme
   « Bloqué » dit « ça attend quelque chose ». Elle est d'ailleurs JUSTE AVANT « À faire » (#921),
   entre ce qui attend quelque chose et ce que quelqu'un a pris ; son rang se décide en JS
   (`pistesDuTableau`), la grille étant peinte par `grid-template-columns` dans l'ordre du DOM. */
.colonne.file-tri {
  border-color: rgba(245, 158, 11, .32);
  background: rgba(245, 158, 11, .06);
}

/* Ce que la colonne ne fait PAS, dit sous elle : la file ignore la recherche, et une colonne qui ne
   bouge pas quand on tape se lirait sinon comme un écran figé. */
.colonne-pied {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
  color: var(--muted2);
  font-size: 10.5px;
  line-height: 1.4;
}

/* La coche d'une carte. Poussée à droite de la ligne du numéro par `margin-left: auto` — elle est le
   seul élément de cette ligne dont la place ne dépend pas de ce qui la précède. */
.tcarte .t-choix {
  margin-left: auto;
  padding: 0 2px;
  border: 0;
  background: none;
  color: var(--muted2);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

.tcarte .t-choix[aria-pressed="true"] {
  color: var(--accent);
}

/* Une carte cochée. Le liseré GAUCHE porte déjà le type et ne peut pas servir : c'est donc le
   contour entier qui change, et il doit rester lisible sur une carte survolée.
   Il est à l'or de la MARQUE et non à `--accent` (#990) : réduit à un projet, l'accent EST la
   couleur du projet, donc celle du contour des étapes de son plan (`.etape-suivie`) — une carte
   cochée ne se distinguait plus d'une étape. `--marque` est le seul or que la teinte ne touche
   jamais, et son contraste est mesuré dans les deux thèmes. */
.tcarte.choisie {
  border-color: var(--marque);
  box-shadow: inset 0 0 0 1px var(--marque);
}

/* « Prendre », sur la carte d'un ticket que personne n'a. Discret : c'est un geste fréquent, pas un
   geste important, et un bouton plein sur trois cents cartes ferait du bruit. */
.tcarte .t-prendre {
  margin-top: 8px;
  padding: 3px 9px;
  border: 1px solid rgba(245, 158, 11, .45);
  border-radius: 999px;
  background: rgba(245, 158, 11, .10);
  color: var(--warn);
  font-size: 11px;
  cursor: pointer;
}

.tcarte .t-prendre:hover {
  background: rgba(245, 158, 11, .20);
}

/* Le bandeau des gestes groupés. Il n'existe qu'avec une sélection — le JS ne le dessine pas
   autrement —, donc aucune marge n'est réservée pour lui.
 *
 * FLOTTANT en bas de la fenêtre depuis #948, et c'est un choix d'UX : une sélection se fait en
 * parcourant les colonnes, qui défilent chacune pour elle-même, et une barre posée entre les
 * filtres et le tableau finissait hors de vue au moment même où l'on voulait agir. Flottante, elle
 * reste sous la main quel que soit le défilement, et elle ne pousse plus le tableau vers le bas
 * quand elle apparaît. Le fond est celui d'un PANNEAU et non un aplat d'accent (#948) : un aplat
 * large dans la teinte des liens tirait l'œil plus que les cartes qu'on vient de cocher. */
.bandeau {
  position: fixed;
  left: 50%;
  bottom: 18px;
  z-index: 45;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  max-width: 94vw;
  padding: 9px 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 60%, var(--line));
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--ombre-flottante);
  transform: translateX(-50%);
}

/* Le glisser d'une carte fait apparaître la zone « Clôturer », au même endroit : les deux barres se
   recouvriraient, et c'est de toute façon le même geste. Le bandeau s'efface donc le temps du
   glisser — `visibility` et non `display`, pour ne pas le redessiner au relâchement. */
#bandeau.pendant-glisser .bandeau {
  visibility: hidden;
}

.bandeau-compte {
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
}

/* « Clôturer » porte la couleur de ce qu'il fait, comme la zone de dépôt du glisser qui mène au même
   endroit (#948) — et seulement quand il est ouvert : un bouton grisé et vert promettrait un geste
   qu'il ne fera pas.
   Sur la FICHE aussi depuis #937 : c'est le même geste, et une teinte qui ne le suivrait pas d'un
   écran à l'autre ferait douter que ce soit bien lui. */
.bandeau .btn-sm[data-geste="cloturer"]:not([disabled]),
.fiche-gestes .btn-sm[data-geste="cloturer"]:not([disabled]) {
  border-color: rgba(34, 197, 94, .5);
  color: var(--encre-ok);
}

.bandeau .btn-sm[data-geste="cloturer"]:not([disabled]):hover,
.fiche-gestes .btn-sm[data-geste="cloturer"]:not([disabled]):hover {
  border-color: var(--ok);
  background: rgba(34, 197, 94, .14);
}

/* Et « Supprimer » en rouge, aux deux mêmes endroits (#939). Ce n'est pas de la décoration : c'est
   le seul geste de ces deux barres qui ne se défait pas, et il y voisine ceux qu'on clique douze
   fois par jour. La teinte le fait viser exprès, au lieu d'être atteint en glissant d'un bouton.
   Grisé, il reste neutre, pour la raison de « Clôturer » : un bouton refusé et rouge annoncerait un
   geste qu'il ne fera pas. */
.bandeau .btn-sm[data-geste="supprimer"]:not([disabled]),
.fiche-gestes .btn-sm[data-geste="supprimer"]:not([disabled]) {
  border-color: rgba(239, 68, 68, .5);
  color: var(--encre-bad);
}

.bandeau .btn-sm[data-geste="supprimer"]:not([disabled]):hover,
.fiche-gestes .btn-sm[data-geste="supprimer"]:not([disabled]):hover {
  border-color: var(--bad);
  background: rgba(239, 68, 68, .14);
}

/* Ce qu'un geste LAISSE de côté, collé à son compte : « Prendre · 3 −2 » se lit d'un coup, là où
   deux nombres séparés obligeraient à chercher lequel va avec quoi. */
.bandeau-acote {
  margin-left: 5px;
  color: var(--muted2);
  font-size: 10.5px;
}

/* La zone « Clôturer » du glisser. FLOTTANTE, en bas de la fenêtre, et c'est la seule chose qui
   compte ici : posée dans le flux, elle apparaîtrait au moment même où la souris tient une carte et
   pousserait le tableau vers le bas — la carte glisserait sous le curseur, et on lâcherait dans la
   mauvaise colonne. Fixe, elle ne déplace rien, elle reste atteignable quelle que soit la position
   du défilement, et la cible est large. */
.depot-cloture {
  position: fixed;
  right: 24px;
  bottom: 24px;
  left: 24px;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px;
  border: 2px dashed rgba(34, 197, 94, .55);
  border-radius: var(--radius);
  background: rgba(21, 46, 32, .96);
  color: var(--ok);
  font-size: 12.5px;
  font-weight: 600;
}

.depot-cloture.depot {
  border-style: solid;
  background: rgba(34, 197, 94, .28);
}

/* ─────────── le plan d'action (#779) ───────────
 *
 * Deux écrans, une seule famille de règles. Ce qui les sépare n'est pas le sens mais la LARGEUR :
 * la colonne de la vue Tickets est étroite, la carte de la fiche a toute la page. Les étapes d'un
 * groupe sont donc posées en `flex` avec une largeur de base — elles se mettent côte à côte là où il
 * y a la place, et l'une sous l'autre là où il n'y en a pas, sans qu'aucun JS ait à le décider.
 */

/* La colonne et le tableau côte à côte. `minmax(0, 1fr)` sur le tableau pour la même raison que le
   `min-width: 0` des colonnes : une piste « auto » s'élargit jusqu'au plus long titre qu'elle
   porte, et le tableau déborderait de la page vers la droite. */
.avec-plan {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
}

/* Les classes sont posées par le JS, qui sait s'il a un plan à montrer et s'il est déroulé. Un
   sélecteur qui déduirait la même chose de l'attribut `hidden` dirait la même chose deux fois — et
   c'est la feuille de style qu'on oublierait le jour où le JS change d'avis.
   `ouvert` = la bande des marque-pages est là ; `deploye` = la colonne du plan (#791).
   La bande n'a PLUS de piste depuis #1084 : en écran large elle est collée au bord gauche de la
   FENÊTRE (#1095, cf. la vue Tickets, plus bas), et en écran étroit elle passe au-dessus. Une piste
   à elle décalait la colonne et le tableau du bord des filtres, pour une bande qui semblait sortir
   de rien. */
.avec-plan.deploye {
  grid-template-columns: 340px minmax(0, 1fr);
}

/* La bande des marque-pages : un par plan, empilés à l'extrême gauche. Elle est `sticky` comme la
   colonne — refermer le plan pour retrouver le tableau n'a de sens que si de quoi le rouvrir reste
   sous les yeux en bas de page. */
.pl-marques {
  position: sticky;
  top: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-self: start;
}

/* Sur un écran étroit, la colonne passe au-dessus du tableau plutôt que de lui manger la moitié, et
   les marque-pages avec elle — en rang, puisqu'ils n'ont plus de bord gauche à border.
   Ce bloc vient APRÈS les règles qu'il surcharge, et ce n'est pas une question de rangement : un
   media query ne pèse rien dans la spécificité. `.pl-marques` y est à égalité (0,1,0) avec sa règle
   de base, donc seule la POSITION dans le document les départage — placé plus haut, il était écrasé
   par le `sticky`/`column` qu'il existe pour défaire, et sans que rien ne le signale (#826). La
   moitié grille, elle, ne le montrait pas : `.avec-plan.deploye` y gagne par spécificité (0,2,0). */
@media (max-width: 1100px) {
  .avec-plan.deploye {
    grid-template-columns: minmax(0, 1fr);
  }

  .pl-marques {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
  }
}

/* La couleur vient du JS : c'est celle du PROJET qui porte le plan, et elle n'est écrite nulle part
   ici. Le bord gauche épais est ce qui fait lire l'onglet comme un marque-page plutôt que comme un
   bouton de plus. */
.pl-marque {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: center;
  padding: 8px 6px;
  border: 1px solid var(--line);
  border-left: 4px solid;
  border-radius: 0 8px 8px 0;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
}

.pl-marque:hover {
  filter: brightness(1.25);
}

.pl-marque.ouvert {
  border-color: currentcolor;
}

.pl-chevron {
  font-size: 13px;
}

/* Le carnet d'un plan, quand les deux partagent le rail de la page du poste (#1821) : sous le numéro,
   à la largeur de l'onglet. Un plan EN LIGNE ne scintille pas au glisser d'une carte du poste (plus
   bas) : il ne la prendrait pas — le même numéro y désigne un autre ticket. */
.pl-marque .badge-carnet {
  margin-left: 0;
}

.pl-marque .badge-carnet .icone {
  width: 11px;
  height: 11px;
}

/* LE SCINTILLEMENT (#1085) : pendant qu'une carte est tenue, les onglets des plans et la colonne
   déroulée disent qu'ils l'accueillent — la déposer confie sa place à un agent. Un halo de la teinte
   des étoiles (`--etoile`, #1079) qui respire, et pas un clignotement : il dure le temps d'un
   glisser, et doit se voir sans tirer l'œil. La classe est posée sur le corps par le JS
   (`montrerLesPlansQuiAccueillent`). Sans mouvement pour qui l'a demandé : le halo reste, fixe. */
@keyframes plan-accueille {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--etoile) 35%, transparent); }
  50% { box-shadow: 0 0 10px 2px color-mix(in srgb, var(--etoile) 75%, transparent); }
}

.glisse-vers-plan .pl-marque:not(.en-ligne),
.glisse-vers-plan .pl-rail:not(.en-ligne) {
  animation: plan-accueille 1.2s ease-in-out infinite;
}

.glisse-vers-plan .pl-marque.depot {
  animation: none;
  box-shadow: 0 0 12px 3px var(--etoile);
  filter: brightness(1.35);
}

@media (prefers-reduced-motion: reduce) {
  .glisse-vers-plan .pl-marque:not(.en-ligne),
  .glisse-vers-plan .pl-rail:not(.en-ligne) {
    animation: none;
    box-shadow: 0 0 8px 2px color-mix(in srgb, var(--etoile) 60%, transparent);
  }
}

/* LA CONSTELLATION du tesseract (#1102) : son dessin est `js/constellation.js`, son MOUVEMENT est ici,
   parce que deux pages la tracent — Terminus quand une session livre, l'app pendant qu'elle s'ouvre
   (#1348). Les étoiles s'allument, les arêtes se tracent, les liens joignent les deux cubes, le cœur
   brille ; `--i` (posé par le script) décale chaque trait sur le précédent. Où elle se pose, sa
   taille et si elle s'éteint, chaque page le dit. */
.constellation { height: auto; overflow: visible; pointer-events: none; }
.constellation line, .constellation .anneau { fill: none; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 1; }
.constellation .etoiles circle { fill: var(--etoile); opacity: 0; transform-box: fill-box; transform-origin: center;
                                 filter: drop-shadow(0 0 4px var(--etoile));
                                 animation: s-allume .4s ease-out forwards; animation-delay: calc(var(--i) * 45ms); }
.constellation .aretes line { stroke: var(--etoile); stroke-width: 2.6;
                              animation: se-trace .45s ease-out forwards; animation-delay: calc(.35s + var(--i) * 28ms); }
.constellation .jonctions line { stroke: var(--marque); stroke-width: 1.8; stroke-opacity: .8;
                             animation: se-trace .4s ease-out forwards; animation-delay: calc(1.1s + var(--i) * 40ms); }
.constellation .anneau { stroke: var(--projet, var(--accent)); stroke-width: 3; stroke-opacity: .7;
                         animation: se-trace 1.4s ease-in-out .2s forwards; }
.constellation .coeur { fill: var(--marque); opacity: 0; transform-box: fill-box; transform-origin: center;
                        filter: blur(14px); animation: eclat 1s ease-out 1.45s both; }
@keyframes s-allume { 0% { opacity: 0; transform: scale(0); } 60% { opacity: 1; transform: scale(1.7); } 100% { opacity: 1; transform: none; } }
@keyframes se-trace { to { stroke-dashoffset: 0; } }
@keyframes eclat { 0% { opacity: 0; transform: scale(.3); } 45% { opacity: .6; transform: scale(1); } 100% { opacity: .25; transform: scale(1.3); } }

/* L'OUVERTURE (#1348) : pendant que l'app charge, la constellation se trace sur le fond, et le nom
   paraît dessous quand elle est presque finie. Elle couvre tout et prend les clics : dessous, le
   squelette est encore vide. `app.js` la lève (`leverLOuverture`) — un fondu, puis elle s'en va.
   Sans mouvement pour qui l'a demandé : la page ne pose pas la constellation, le nom seul. */
.ouverture {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  background: var(--bg);
  transition: opacity .4s ease-out;
}

.ouverture.levee {
  opacity: 0;
  pointer-events: none;
}

.ouverture .constellation { width: min(46vmin, 300px); }

.ouverture-nom {
  margin: 0;
  font-size: clamp(28px, 6vmin, 42px);
  animation: ouverture-nom .7s ease-out 1.1s both;
}

@keyframes ouverture-nom { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .ouverture { transition: none; }
  .ouverture-nom { animation: none; }
}

/* Le bord gauche épais porte la couleur du plan, posée par le JS (#775) : la colonne, son
   marque-page et les cartes de ses étapes sont alors la même chose vue de trois endroits. Elle ne
   la portait pas du tout — le panneau était neutre, donc un marque-page coloré désignait une colonne
   qui ne lui répondait rien, et c'est ce maillon manquant qui rendait le lien illisible.
   À GAUCHE, comme le marque-page qui la déploie et comme la puce d'une carte : c'est le même trait,
   qui se prolonge d'un bout à l'autre de la bande. */
.pl-rail {
  position: sticky;
  top: 12px;
  max-height: calc(var(--haut-ecran) - var(--barre-fenetre, 0px) - 160px);
  overflow-y: auto;
  padding: 12px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius);
  background: var(--panel);
}

.pl-rail-tete {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

/* `flex: 1` et `min-width: 0` vont ensemble : sans le second, un élément en `nowrap` refuse de
   descendre sous la largeur de son texte, et la coupure par points de suspension ne se produit
   jamais — le titre pousserait la colonne au lieu d'y tenir. */
.pl-titre {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: 13px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}

.pl-titre:hover {
  color: var(--accent);
}

.pl-rail-tete .tnum {
  cursor: pointer;
}

/* Replier la colonne depuis sa propre tête (#918) : quand le plan est seul, la bande n'est plus là
   pour le faire. */
.pl-replier {
  flex: none;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  line-height: 20px;
  cursor: pointer;
}

.pl-replier:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* DEUX niveaux, parce qu'un plan porte deux avancements (#889) : le TRAVAIL — les étapes traitées,
   qui attendent une relecture — et la RELECTURE elle-même, les étapes closes. Un seul remplissage
   obligeait à choisir, et les deux choix mentent : celui des closes annonçait « rien n'a bougé » sur
   un plan entièrement déroulé par des sessions (aucune ne clôt de ticket), celui des traitées
   annoncerait « fini » sur un plan dont personne n'a relu une ligne.
   Elles se SUPERPOSENT au lieu de se suivre : une étape close est aussi une étape traitée, et les
   mettre bout à bout ferait déborder la jauge dès la première relecture. */
.pl-jauge {
  position: relative;
  overflow: hidden;
  height: 5px;
  margin-top: 9px;
  border-radius: 999px;
  background: var(--panel2);
}

.pl-jauge span {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background: var(--ok);
}

/* La même teinte pour les deux, en retrait pour le travail non relu : c'est un seul avancement lu à
   deux moments, pas deux mesures qui se disputent la lecture. */
.pl-jauge .traitees {
  opacity: .42;
}

.pl-compte,
.pl-pied,
.pl-vide {
  margin-top: 6px;
  color: var(--muted2);
  font-size: 11px;
}

.pl-pied {
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

/* ─────────── la vue Tickets tient dans l'écran (#989) ───────────
 *
 * La page ne défile plus : la barre, les onglets et les filtres prennent leur hauteur, et le
 * tableau et la colonne du plan se partagent TOUT le reste — on défile dans une colonne, et on en
 * voit toujours le bas. Les plafonds recopiés (`100vh - 300px` pour les colonnes, `100vh - 160px`
 * pour le plan) n'étaient vrais que pour une hauteur de filtres, et les deux bas ne tombaient pas
 * au même endroit ; ici c'est la mise en page qui mesure.
 * Chaque maillon porte `min-height: 0` : un élément flex ou grille ne descend pas, sinon, sous la
 * hauteur de son contenu, et c'est la page qui se remettrait à défiler.
 * Pas sous 1100 px : la colonne du plan y passe AU-DESSUS du tableau, et deux blocs empilés dans
 * un seul écran n'y laisseraient de place à aucun — le défilement de page y reste, avec ses
 * plafonds. `:has` plutôt qu'une classe de plus : `.active`, posé par `afficherOnglet`, dit déjà
 * quelle vue est affichée. */
@media (min-width: 1101px) {
  body:has(#vue-tickets.active) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  body:has(#vue-tickets.active) main {
    display: flex;
    flex: 1;
    flex-direction: column;
    width: 100%;
    min-height: 0;
  }

  #vue-tickets.active {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
  }

  #vue-tickets > * {
    flex: none;
  }

  #vue-tickets > .avec-plan {
    flex: 1;
    grid-template-rows: minmax(0, 1fr);
    min-height: 0;
  }

  /* LES MARQUE-PAGES SONT COLLÉS AU BORD DE LA FENÊTRE (#1095, après la marge du contenu de
     #1084) : hors de la grille, la colonne du plan — ou, repliée, la première colonne du tableau —
     reste alignée sur les filtres et la barre des projets. La marge de `main` s'élargit de la bande
     dès qu'un plan est là, qu'elle soit affichée ou non : sinon toute la vue sauterait de côté au
     premier repli — et c'est elle qui empêche la bande de recouvrir le contenu tant que `main`
     touche le bord (en deçà de sa largeur maximale). */
  body:has(#vue-tickets.active) {
    --bande-marques: 52px;
  }

  body:has(#vue-tickets.active .avec-plan:is(.ouvert, .deploye)) main {
    padding-left: calc(22px + var(--bande-marques));
  }

  /* `fixed` et `left: 0` plutôt qu'un décalage compté depuis `main` : centré sous sa largeur
     maximale, `main` n'est pas au bord, et le compter recopierait sa géométrie ici. SANS `top` : la
     bande garde sa position STATIQUE, le haut de la grille, donc le haut de la colonne qu'elle
     déroule — et la page ne défile pas à cette largeur (#989), donc rien ne l'en décolle. Le bord
     plat et épais de la règle de base est alors contre l'écran, et l'arrondi vers le contenu. */
  #vue-tickets #plan-marques {
    position: fixed;
    left: 0;
    width: var(--bande-marques);
  }

  #vue-tickets #tableau {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  #vue-tickets .kanban {
    flex: 1;
    grid-auto-rows: minmax(0, 1fr);
    min-height: 0;
  }

  #vue-tickets .colonne {
    min-height: 0;
  }

  #vue-tickets .colonne-corps {
    min-height: 0;
    max-height: none;
  }

  /* La colonne du plan tombe au bas du tableau : plus de `sticky` (rien ne défile sous elle), et
     seules ses étapes défilent — la tête, la jauge et le pied restent. */
  #vue-tickets #plan-rail {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  #vue-tickets .pl-rail {
    position: static;
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
    max-height: none;
    overflow: hidden;
  }

  #vue-tickets .pl-rail > * {
    flex: none;
  }

  #vue-tickets .pl-rail > .pl-defile {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding-right: 3px;
  }
}

/* ─────────── l'arbre : des groupes qui se suivent ─────────── */

.pl-arbre {
  margin-top: 10px;
}

/* Un groupe : la carte COMMUNE qui porte ses sous-cartes. Elle reste visible même à une seule
   étape — c'est ce qui donne à l'arbre des marches régulières, et c'est sur elle qu'on dépose. */
.pl-groupe {
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  transition: border-color .12s, background .12s;
}

/* Un groupe parallèle se voit AVANT d'être lu : deux étapes côte à côte sans liseré ressembleraient
   à deux étapes qui se suivent, et c'est exactement ce qu'on veut cesser de croire. */
.pl-groupe.parallele {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* « N étapes en parallèle » se lit SOUS ce voile, et `--muted2` y tombait à 3,7 (#1137). */
.pl-groupe.parallele .hint {
  color: var(--muted);
}

.pl-groupe.depot {
  border-color: var(--ok);
  background: rgba(34, 197, 94, .12);
}

.pl-g-tete {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 6px;
}

.pl-g-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--panel2);
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
}

.pl-g-corps {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* L'interstice ENTRE deux groupes : la place où une étape reprend son tour. Il est mince au repos —
   une bande de 10 px suffit à viser — et s'allume dès qu'un glisser l'accepte. */
.pl-entre {
  height: 10px;
  margin: 2px 0;
  border-radius: 999px;
}

.pl-entre.depot {
  height: 16px;
  background: var(--ok);
}

/* ─────────── une étape ─────────── */

.pl-etape {
  flex: 1 1 200px;
  min-width: 0;
  padding: 7px 8px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: 8px;
  background: var(--panel2);
  cursor: grab;
}

.pl-etape:hover {
  border-color: var(--accent);
}

/* Qui ne peut pas tenir le plan ne voit pas une main qui saisit : elle annoncerait un geste que le
   serveur refuserait. L'étape reste cliquable — elle ouvre la fiche du ticket. */
.pl-etape[draggable="false"] {
  cursor: pointer;
}

.pl-etape.glisse {
  opacity: .5;
}

/* L'étape de la session qu'on regarde dans Terminus (#1288) : un CONTOUR, qui ne touche ni le
   liseré de type ni le trait d'un jalon — c'est la bordure qui les porte. */
.pl-etape.courante {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Une RECETTE ne se fait pas, elle VÉRIFIE (#781) — et dans un plan qui mélange les deux, il faut
   pouvoir lire le parcours sans lire les titres. Le trait DISCONTINU porte l'essentiel du signal :
   la couleur seule redirait le liseré de type (huit teintes s'y disputent déjà la lecture) et ne
   dirait rien à qui ne la distingue pas. Le bord GAUCHE reste plein : c'est celui du type, il tient
   la colonne d'alignement de toutes les étapes, et le pointiller le ferait sauter d'une étape à
   l'autre. Rien de tout ça ne touche aux cartes du tableau, qui portent déjà le badge du type.

   Aucune teinte n'est écrite ici, et c'est la leçon de #775 : le magenta de la puce d'étape était
   un littéral posé dans cette feuille, et il ne pouvait plus suivre la table des types. La couleur
   arrive donc du JS, qui la tient de `TYPES` — la même source que le liseré juste au-dessus. */
.pl-etape.recette {
  border-style: dashed;
  border-left-style: solid;
}

/* Un DÉPLOIEMENT met en service ce qui précède (#1143) : c'est là qu'une version part. Même logique
   que la recette — la forme porte le signal avant la couleur —, mais une forme À LUI : un trait
   double, pour que les deux sortes de jalon ne se lisent pas l'une pour l'autre. Le bord gauche reste
   plein, pour la raison dite juste au-dessus ; sa teinte arrive du JS, comme celle de la recette. */
.pl-etape.deploiement {
  border-style: double;
  border-left-style: solid;
  border-top-width: 3px;
  border-right-width: 3px;
  border-bottom-width: 3px;
}

.pl-jalon {
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .02em;
  white-space: nowrap;
}

/* Une étape faite s'efface sans disparaître : elle compte encore dans le parcours, et la retirer de
   l'écran ferait croire que le plan raccourcit à mesure qu'on avance.
   L'estompage se pose sur ses ENFANTS, pas sur la carte, et le lot d'un déploiement y échappe
   (#1179) : une opacité se compose, aucun enfant ne sort de celle de son parent, et la version
   tombait à 3,0 en sombre et 2,3 en clair — sur un déploiement terminé, c'est elle qu'on vient
   lire. Le cadre de la carte reste donc plein ; le titre barré et la pastille disent « faite ». */
.pl-etape.faite > :not(.pl-lot) {
  opacity: .55;
}

.pl-etape.faite .pl-t {
  text-decoration: line-through;
}

/* Une étape TRAITÉE s'efface MOINS (#889) : le travail est fait, mais elle attend une relecture —
   la barrer serait dire qu'il n'y a plus rien à en faire, et la laisser pleine ferait chercher du
   travail là où il n'y en a plus. Elle garde son titre lisible, et c'est la pastille d'état de sa
   tête qui dit lequel des deux — la nuance d'opacité seule ne dirait rien à qui ne la voit pas.
   Même mécanisme que la faite, pour la même raison (#1179). */
.pl-etape.traitee > :not(.pl-lot) {
  opacity: .78;
}

/* Une étape close SANS SUITE (#1782) s'efface comme une faite, mais en italique et sans être barrée :
   barrer dirait « fait », et elle n'a rien livré. */
.pl-etape.sans-suite > :not(.pl-lot) {
  opacity: .45;
  font-style: italic;
}

.dep-badge.sans-suite {
  font-style: italic;
}

/* Elle ENROULE depuis qu'une étape peut porter un marqueur de plus (#781) : la colonne du plan est
   étroite, et trois pastilles sur une ligne qui ne se coupe pas repousseraient la croix hors du
   cadre — un bouton qui sort de sa carte ne lève pas, il devient seulement impossible à viser. */
.pl-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.pl-tete .compteur {
  padding: 1px 6px;
  font-size: 10px;
}

.pl-x {
  margin-left: auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--muted2);
  cursor: pointer;
  font-size: 12px;
}

.pl-x:hover {
  color: var(--bad);
}

/* ✓ d'une étape (#1725), juste avant la croix : c'est lui qui prend la marge automatique, et la
   croix qui le suit se range contre lui. */
.pl-clore {
  margin-left: auto;
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--muted2);
  cursor: pointer;
  font-size: 12px;
}

.pl-clore:hover {
  color: var(--ok);
}

.pl-clore + .pl-x {
  margin-left: 0;
}

.pl-t {
  margin-top: 3px;
  font-size: 12px;
  overflow-wrap: anywhere;
}

.pl-note {
  margin-top: 3px;
  color: var(--muted2);
  font-size: 11px;
}

/* La colonne est étroite : la note y prendrait cinq lignes pour un détail qui se lit sur la fiche,
   où le plan a toute la place. Le JS n'en sait rien — c'est une affaire de largeur, pas de sens. */
.pl-rail .pl-note {
  display: none;
}

/* Ce qu'un déploiement met en service (#1162) : la version posée et les étapes qu'elle contient.
   Elle RESTE dans la colonne, contrairement à la note : c'est ce qu'on vient y chercher, et elle
   enroule au lieu de déborder. */
.pl-lot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
  margin-top: 4px;
  font-size: 11px;
}

.pl-version {
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-weight: 700;
  white-space: nowrap;
}

/* Ce qu'une recette a conclu (#1340, #1540). Le MOT porte le sens (« échec », « réussite »,
   « incomplète »), la couleur ne fait que le souligner — elle ne dit rien à qui ne la voit pas. */
.pl-verdict {
  font-weight: 700;
  white-space: nowrap;
}

.pl-verdict.reussite {
  color: var(--ok);
}

.pl-verdict.echec {
  color: var(--bad);
}

.pl-verdict.incomplete {
  color: var(--warn);
}

/* Une recette INCOMPLÈTE attend une personne (#1540) : son cadre le dit, et ses points se lisent
   un par un, chacun avec ses deux boutons. */
.pl-etape.attend-personne {
  outline: 1px dashed var(--warn);
  outline-offset: -1px;
}

/* La checklist d'une recette (#2060) : l'état en tête de ligne, pour se lire en colonne sans lire
   les points ; ce qu'une session propose, sous son point, en retrait. */
.pl-points {
  margin: 4px 0;
  padding-left: 18px;
}

.pl-points li {
  margin: 2px 0;
}

.pl-points .btn-sm {
  margin-left: 4px;
}

.pl-proposition {
  margin: 2px 0 4px;
  padding-left: 8px;
  border-left: 2px solid var(--warn);
  font-size: 0.92em;
}

/* Une recette À REJOUER revient au travail : pleine, sans estompage, et son cadre le dit. */
.pl-etape.a-rejouer {
  outline: 1px dashed var(--bad);
  outline-offset: -1px;
}

.pl-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}

/* La puce d'une carte du tableau qui est une étape : de quel plan elle relève, et où elle en est,
   sans l'ouvrir.
   Elle CITE le marque-page de son plan, et par la FORME autant que par la couleur : même chevron,
   même `#id`, même bord gauche épais, même coin gauche carré — un onglet, pas une pastille. La
   couleur, elle, vient du JS (`couleurDuPlan`) : écrite ici, c'était un magenta en dur, donc la
   teinte du TYPE « plan d'action » et jamais celle du plan. La carte ne pouvait alors pas rappeler
   sa colonne, par construction (#775). Le repli de `border-color`/`color` sert au seul cas où le
   porteur n'est pas chargé : une couleur neutre, et non une qui aurait l'air d'être la sienne. */
.plan-puce {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 1px 7px 1px 5px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--muted2);
  border-radius: 0 999px 999px 0;
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  white-space: nowrap;
}

/* Le rang de l'étape dans son plan — « 3/9 ». En retrait de teinte pour que le `#id`, qui est ce
   qu'on rapproche du marque-page, reste ce qu'on lit d'abord. */
.plan-puce .pp-rang {
  opacity: .72;
  font-weight: 700;
}

/* Une carte du tableau qui est une étape du plan DÉROULÉ (#775). Le halo est ce qui fait « ressortir
   rapidement » : une puce se cherche carte par carte, un liseré coloré à travers trois colonnes se
   voit sans chercher. `--plan-teinte` est posée par le JS avec la classe — c'est la couleur du plan,
   donc celle de sa colonne et de son marque-page.
   Le bord GAUCHE reste celui du type de ticket : il dit ce qu'est la carte, et l'écraser ferait
   perdre une information pour en ajouter une autre. C'est donc le CONTOUR et l'ombre qui portent le
   plan — deux propriétés que rien d'autre n'utilise sur cette carte.
   Cette règle doit rester APRÈS `.tcarte:hover`, et ce n'est pas du rangement : les deux sont à
   spécificité égale (0,2,0), donc seule la position dans le document les départage. Placée plus
   haut, le survol reprendrait la couleur d'accent et effacerait le lien au plan — exactement sur la
   carte qu'on est en train de rapprocher de sa colonne, et sans que rien ne le signale. C'est le
   piège déjà payé par `.pl-marques` en écran étroit (#826). */
.tcarte.etape-suivie {
  border-color: var(--plan-teinte);
  box-shadow: 0 0 0 1px var(--plan-teinte), 0 2px 14px -4px var(--plan-teinte);
}

/* Une étape du plan suivi, COCHÉE (#990) : la règle ci-dessus, placée après `.tcarte.choisie`,
   effaçait la sélection sur ces cartes-là. Les deux se disent : le contour or de la sélection, et
   le halo du plan autour. */
.tcarte.etape-suivie.choisie {
  border-color: var(--marque);
  box-shadow: inset 0 0 0 1px var(--marque), 0 0 0 1px var(--plan-teinte),
    0 2px 14px -4px var(--plan-teinte);
}

/* ─────────── rien à afficher ─────────── */

/* Un écran vide doit dire POURQUOI il est vide et quoi faire : la base est neuve, aucun écran ne
   sait encore créer un projet, voilà la commande. Un vide muet se lit comme une panne. */
.vide {
  max-width: 520px;
  margin: 40px auto;
  padding: 28px;
  border: 1px dashed var(--line);
  border-radius: 14px;
  background: var(--panel);
  text-align: center;
}

/* La phrase d'ambiance d'un écran vide (#775) : en retrait, en italique, sous la phrase utile. Elle
   doit se lire APRÈS, jamais à la place — c'est ce qui la rend supportable au centième passage. */
.vide .asimov {
  margin-bottom: 0;
  color: var(--muted2);
  font-size: 12px;
  font-style: italic;
}

.vide .ic {
  font-size: 40px;
}

.vide h3 {
  margin: 8px 0 6px;
}

.vide p {
  margin: 0;
  color: var(--muted);
}

/* ─────────── bandeaux du bas ─────────── */

/* Des `popover` (#1915) : dans la couche du dessus, par-dessus une boîte modale et son voile. Le
   navigateur les centre d'office (`inset: 0; margin: auto`) et les borde : `top`, `right`, `margin`
   et `border` sont rendus à ce que ces bandeaux étaient. */
.bandeau-erreur,
.toast {
  position: fixed;
  top: auto;
  right: auto;
  bottom: 18px;
  left: 50%;
  z-index: 80;
  margin: 0;
  border: 0;
  transform: translateX(-50%);
  max-width: 80vw;
  padding: 10px 16px;
  border-radius: 10px;
  box-shadow: var(--shadow);
}

.bandeau-erreur {
  background: rgba(239, 68, 68, .95);
  color: #fff;
}

.toast {
  border: 1px solid var(--line);
  background: var(--panel2);
  color: var(--txt);
}

/* ─────────── la porte : page de connexion et premier compte ───────────
   Sur sa propre page (connexion.html), donc sans rien du tableau : ni barre du haut, ni onglets.
   Les couleurs restent celles des variables — une page d'entrée qui aurait ses propres teintes
   donnerait l'impression d'avoir changé d'application au moment d'entrer. */

.page-porte {
  display: grid;
  place-items: center;
  padding: 24px;
}

.porte {
  width: 100%;
  max-width: 380px;
}

.porte-marque {
  margin-bottom: 18px;
  text-align: center;
}

.porte-marque h1 {
  font-size: 30px;
}

.porte-marque .sub {
  margin: 4px 0 0;
  color: var(--muted2);
  font-size: 12px;
}

.porte-bloc {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  box-shadow: var(--shadow);
}

.porte-bloc h2 {
  margin: 0;
  font-size: 16px;
}

.porte-bloc label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--muted);
  font-size: 12px;
}

.porte-bloc input {
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-family: inherit;
  font-size: 14px;
}

.porte-bloc input:focus {
  border-color: var(--accent);
  outline: none;
}

.porte-option {
  color: var(--muted2);
}

.porte-mot {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
}

.btn-porte {
  margin-top: 4px;
  padding: 9px 12px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: var(--accent);
  color: var(--sur-accent);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

/* Le geste SECONDAIRE mis en avant (#918) : un contour d'accent, et non un aplat. Un écran n'a qu'un
   bouton plein — celui qui valide ce qu'on est en train de faire ; « Nouveau ticket », « Nouvelle
   personne » ou « Lancer le contrôle » ouvrent un geste sans le conclure, et quatre aplats cyan sur
   le même écran se disputaient l'œil sans rien hiérarchiser. Le survol ne fonce que le contour,
   comme `.btn-sm` (#1139) : un voile plus dense sous l'accent le faisait tomber à 4,1 en clair. */
.btn-accent {
  padding: 6px 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  border-radius: 8px;
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--panel2);
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.btn-accent:hover {
  border-color: var(--accent);
}

.btn-porte:hover {
  filter: brightness(1.08);
}

.porte-etat {
  color: var(--muted2);
  text-align: center;
}

/* ─────────── qui est connecté, dans Paramètres › Moi (#1302, #1996) ─────────── */

.moi {
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
  font-size: 12px;
}

/* La boîte de dialogue du changement de mot de passe. <dialog> natif : il gère le fond modal, la
   touche Échap et le retour du focus — trois choses qu'une div recouvrante oblige à réécrire. */
.dlg {
  /* Assez large pour les boîtes de l'annuaire, qui portent jusqu'à six champs. */
  width: min(94vw, 420px);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  color: var(--txt);
}

.dlg::backdrop {
  background: var(--voile);
}

.dlg form {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px;
  /* Une boîte plus haute que l'écran DÉFILE, au lieu de déborder hors du champ (#722). Un <dialog>
     est centré : ce qui dépasse sort en haut ET en bas, et le bouton « Créer » devient
     inatteignable — la page, elle, ne défile pas derrière un modal. Posé ici et non sur la seule
     boîte qui a grandi : c'est la règle de toutes, et la prochaine qui gagne un champ n'aura pas à
     la redécouvrir. Rien ne change tant que le contenu tient : aucune barre n'apparaît. */
  max-height: 86vh;
  overflow-y: auto;
}

.dlg h2 {
  margin: 0;
  font-size: 16px;
}

.dlg label {
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--muted);
  font-size: 12px;
}

/* La boîte d'un TICKET (#948) : plus large, parce que son formulaire range les champs courts en
   rangées et que sa description fait huit lignes. */
.dlg-large {
  width: min(94vw, 640px);
}

/* Une RANGÉE de champs courts, la disposition d'Artis Tickets : type, priorité, effort d'un côté,
   échéance et estimation de l'autre. `flex-wrap` et une base de 130 px — sur un écran étroit, ils
   repassent l'un sous l'autre au lieu d'être serrés jusqu'à l'illisible. */
.dlg-rangee {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.dlg-rangee > label {
  flex: 1 1 130px;
  min-width: 0;
}

/* « Réglage fin » (#1998) : la grille exacte derrière un préréglage — le niveau et le rôle d'un
   profil, l'accès des sessions par mode. Un <details> ne transmet pas l'espacement du formulaire à
   ce qu'il contient : son corps le reprend, comme le bloc du profil qui range menu, phrase et volet. */
.reglage-fin > summary {
  cursor: pointer;
  color: var(--txt2);
  font-size: 12px;
}

.reglage-fin-corps,
.profil {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.reglage-fin-corps {
  margin-top: 10px;
  padding-left: 10px;
  border-left: 2px solid var(--line);
}

/* L'INTITULÉ d'un champ, en petites capitales : il se distingue de la valeur qu'on tape sans peser
   comme un titre, et c'est ce qui laisse lire une rangée de cinq champs d'un coup d'œil. Posé sur
   la boîte LARGE et non sur toutes : ailleurs, un intitulé est parfois une phrase entière (« À
   prendre — n'attribuer ce ticket à personne »), qui en capitales se lirait comme un avertissement. */
.dlg-large > form > label,
.dlg-large .dlg-rangee > label {
  letter-spacing: .4px;
  text-transform: uppercase;
}

.dlg-large > form > .dlg-case {
  letter-spacing: normal;
  text-transform: none;
}

/* Le textarea partage la règle de l'input, et n'en a pas une à lui : dans une boîte de dialogue,
   les deux sont le même objet — un champ de saisie sous son libellé. Deux déclarations divergeraient
   au premier changement de teinte, et c'est celle qu'on ne regarde pas qui resterait en arrière. */
.dlg input,
.dlg textarea {
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-family: inherit;
  font-size: 14px;
}

/* Verticalement seulement : élargi, il sort de la boîte, qui a une largeur fixe. */
.dlg textarea {
  resize: vertical;
}

/* Les boutons restent VISIBLES quand la boîte défile (#993) : c'est le formulaire entier qui défile
   (`.dlg form`), et sans ça « Créer » passait sous le pli dès que la boîte dépassait l'écran — il
   fallait descendre pour valider. Collés au bas de la zone qui défile, et non sortis du <form> : ce
   sont ses boutons, et un « submit » hors du formulaire ne le soumettrait plus. Le décalage négatif
   rattrape la marge intérieure de ce qui défile (20 px, `.dlg form` comme `.dlg-partage`), et le
   fond cache ce qui passe dessous. Réservé aux BOÎTES : la même rangée sert aussi dans des cartes
   des Paramètres, qui ne défilent pas et n'ont pas cette marge. */
/* Le bloc « Jumelage DEV / Test » d'un projet (#1974) : une paire n'est pas un champ du projet, et
   ses gestes partent sans « Enregistrer » — un filet le sépare du formulaire qui le porte. */
.jumelage {
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

.jumelage > div {
  display: grid;
  gap: 10px;
}

/* La boîte d'un projet a des VOLETS (#1997) : son formulaire, et « Code et déploiement » à côté — un
   frère, parce que la forge et le déploiement ont chacun leur formulaire et qu'un formulaire ne
   s'imbrique pas dans un autre. Le titre et la barre restent en tête, c'est le volet qui défile :
   sa hauteur rend à la tête ce qu'elle prend. Plus large que `.dlg` : les aides de ces champs sont
   des phrases. */
.dlg-a-volets {
  width: min(96vw, 560px);
}

.dlg-tete {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 20px 20px 0;
}

.dlg-a-volets > form,
.dlg-volet {
  max-height: calc(86vh - 84px);
}

.dlg-volet {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 20px;
  overflow-y: auto;
}

/* `display: flex` l'emporterait sur l'attribut : un volet caché resterait à l'écran. */
.dlg-a-volets > form[hidden],
.dlg-volet[hidden] {
  display: none;
}

/* Les formulaires DU volet ne sont pas la boîte : ni sa marge, ni son défilement, ni ses boutons
   collés au bas — ceux-là sont à « Fermer », le dernier du volet. */
.dlg .dlg-volet form {
  padding: 0;
  max-height: none;
  overflow: visible;
}

.dlg .dlg-volet form .dlg-boutons {
  position: static;
  margin: 0;
  padding: 0;
  border-top: 0;
  flex-wrap: wrap;
}

.dlg-volet #deploiement-detail {
  border-top: 1px solid var(--line);
  padding-top: 10px;
}

.dlg-boutons {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.dlg .dlg-boutons {
  position: sticky;
  bottom: -20px;
  margin: 0 -20px -20px;
  padding: 12px 20px 20px;
  border-top: 1px solid var(--line);
  background: var(--panel);
}

/* ─────────── vue Annuaire ─────────── */

/* Une grille et non un <table> : les colonnes doivent rester alignées d'une ligne à l'autre, et
   c'est tout ce qu'on lui demande. Un tableau apporterait en plus une sémantique de données
   tabulaires que rien ici n'exploite, et des règles de bordure à défaire une par une.
 *
 * Les pistes sont déclarées ICI, sur le tableau, et jamais sur la ligne (#891). Une ligne qui porte
 * sa propre grille ne PARTAGE rien : elle redimensionne les mêmes pistes pour elle seule, avec son
 * contenu à elle. La cinquième est vide dans l'en-tête et porte jusqu'à cinq boutons dans une ligne
 * de données — l'en-tête avait donc quatre cents pixels de plus à distribuer à ses pistes en `fr`,
 * et « PORTE » tombait au-dessus des boutons, son chiffre sous la colonne d'à côté. Rien ne lève :
 * les deux lignes sont des grilles valides, elles ne parlent simplement pas des mêmes colonnes.
 * Déclarées sur le tableau, elles sont mesurées UNE fois sur toutes les lignes à la fois — la piste
 * des boutons vaut la plus large d'entre elles, et aucune largeur n'est écrite en dur, ce qu'un
 * libellé de bouton démentirait au premier changement. */
.annuaire {
  display: grid;
  grid-template-columns: minmax(200px, 2.4fr) minmax(120px, 1.2fr) minmax(130px, 1fr) minmax(100px, 1fr) minmax(120px, 1.4fr) auto;
  column-gap: 10px;
  row-gap: 2px;
}

/* `subgrid` plutôt que `display: contents` : la ligne garde son cadre, son fond et son padding —
   c'est ce qui la distingue de l'en-tête et d'une fiche désactivée. Son padding est identique sur
   toutes les lignes, donc les lignes de grille intermédiaires tombent au même endroit partout.
   Et pas de `gap` ici : il vient du tableau, le redéclarer désalignerait ce qu'on vient d'aligner. */
.ann-ligne {
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  align-items: center;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  font-size: 13px;
}

.ann-tete {
  border-color: transparent;
  background: none;
  color: var(--muted2);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .06em;
}

/* Une fiche désactivée reste LISIBLE, juste en retrait : elle n'est pas une erreur, et devoir
   plisser les yeux pour la relire rendrait pénible le seul écran d'où on peut la réactiver. */
.ann-ligne.inactive {
  background: var(--bg);
  color: var(--muted2);
}

/* L'en-tête de la page Personnes (#1709) : l'organisation active et, pour son admin, son quota. */
.personnes-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  margin-bottom: 10px;
}

.personnes-tete h2 {
  margin: 0;
  font-size: 18px;
}

.personnes-tete .hint {
  flex-basis: 100%;
  margin: 0;
}

/* Le choix de l'organisation réglée (#1965), poussé à droite du nom. */
.personnes-tete .personnes-choix {
  margin-left: auto;
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.ann-ligne .qui-courriel {
  display: block;
  font-size: 12px;
}

/* Les équipes d'une personne (#1710) : ses étiquettes, puis le menu qui l'ajoute à une autre. */
.ann-equipes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}

.ann-equipes select {
  max-width: 100%;
  font-size: 12px;
}

.ann-actions {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 6px;
}

/* Au téléphone (#1604), la fiche devient une carte, comme la vue Liste des projets : la personne en
   tête, compte, rôle et niveau dessous, les boutons en bas. Sans en-tête, le rôle et le niveau ne
   diraient plus lequel est lequel : ils portent leur libellé. Un courriel est un mot sans coupure —
   il se coupe. */
@media (max-width: 600px) {
  .annuaire {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .ann-ligne {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 12px;
  }

  .ann-ligne > * {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  .ann-tete {
    display: none;
  }

  .ann-ligne > .qui,
  .ann-actions {
    flex-basis: 100%;
  }

  .ann-ligne > :nth-child(3)::before {
    content: "Rôle : ";
  }

  .ann-ligne > :nth-child(4)::before {
    content: "Niveau : ";
  }

  .ann-ligne > .ann-equipes::before {
    content: "Équipes : ";
  }

  .ann-actions {
    justify-content: flex-start;
  }
}

.acces {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel2);
  font-size: 11px;
}

/* Un accès fermé se voit AVANT d'ouvrir la boîte : c'est la première chose qu'on vient vérifier
   quand quelqu'un dit qu'il n'arrive plus à se connecter. */
.acces.ferme {
  border-color: var(--bad);
  color: var(--bad);
  text-decoration: line-through;
}

.muet {
  color: var(--muted2);
}

/* Deux fiches qui se ressemblent. Ambre quand le rapprochement est sûr (même courriel), plus
   discret quand il ne l'est pas (même nom, donc peut-être deux homonymes) : la couleur dit à quel
   point il faut y croire, sans qu'on ait à relire le motif. */
.doublon {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  padding: 8px 12px;
  border: 1px solid var(--warn);
  border-radius: 10px;
  background: rgba(245, 158, 11, .10);
  color: var(--txt);
  font-size: 12px;
}

.doublon.faible {
  border-color: var(--line);
  background: var(--panel2);
  color: var(--muted);
}

.doublon button {
  margin-left: auto;
}

/* ─────────── les groupes de travail (#727) ───────────
   Dans l'onglet Annuaire, au-dessus de la liste : c'est ce bloc qui décide de ce qu'elle contient.
   Volontairement discret — un groupe n'est pas une alerte, c'est un réglage de carnet d'adresses. */
.groupes {
  margin-bottom: 14px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel2);
}

.groupes-tete {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
}

.groupes-tete h3 {
  margin: 0;
  font-size: 13px;
}

.groupes-tete p {
  flex: 1 1 320px;
  margin: 0;
  font-size: 11.5px;
}

.groupes-liste {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 10px;
}

/* Les organisations de l'instance, une carte de l'onglet Instance des Paramètres (#1916). */
.organisation-ligne {
  padding: 6px 0;
  border-top: 1px solid var(--line);
}

.organisation-comptes {
  margin-left: 12px;
}

.organisation-comptes > summary {
  cursor: pointer;
  color: var(--txt2);
  font-size: 12px;
}

/* « Créer ce jeton » (#2041) : replié sous sa ligne — on y vient pour créer, pas pour lire la liste. */
.jeton-creer {
  margin-top: 6px;
  font-size: 12px;
}

.jeton-creer > summary {
  cursor: pointer;
  color: var(--txt2);
}

.jeton-usages {
  margin: 4px 0 0;
  padding-left: 18px;
}

.jeton-usages li {
  margin-bottom: 4px;
}

.jeton-commande {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0 0;
}

.groupe {
  flex: 1 1 280px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
}

/* Fermé, pas supprimé : il reste lisible, et il se rouvre. L'opacité le dit sans le cacher. */
.groupe.ferme {
  opacity: .6;
}

.groupe-tete {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
}

.groupe-tete .muet {
  margin-left: auto;
  font-size: 11px;
}

.groupe-membres,
.groupe-gestes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
}

.puce-personne {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 6px 2px 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 11.5px;
}

.puce-personne button {
  padding: 0 4px;
  border: 0;
  background: transparent;
  color: var(--muted2);
  cursor: pointer;
}

.groupe-ajout {
  display: inline-flex;
  gap: 6px;
}

.groupe-ajout select {
  max-width: 180px;
  padding: 4px 7px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-size: 11.5px;
}

/* Un <select> que le navigateur habillerait autrement que les <input> si on ne le disait pas. */
.dlg select {
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-family: inherit;
  font-size: 14px;
}

.dlg p {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
}

/* Ce qu'une suppression emporte (#846). Une LISTE et non un paragraphe : chaque ligne est une chose
   qu'on perd, et noyées dans une phrase elles se lisent comme un avertissement de plus qu'on
   parcourt sans compter. Le reste du texte de la boîte reste en `.dlg p`, plus discret — ce qui part
   doit se distinguer de ce qui l'explique. */
.dlg .liste-sobre {
  margin: 0;
  padding-left: 18px;
  color: var(--txt);
  font-size: 13px;
  line-height: 1.6;
}

/* ─────────── partager un projet (#773) ─────────── */

/* Les personnes avec qui le projet est partagé, sur la carte : le propriétaire d'abord, puis les
   invités. Des pastilles et non des noms — une carte de projet se lit d'un coup d'œil, et cinq noms
   en toutes lettres la feraient grandir sans rien dire de plus (le titre de survol les donne). */
.partages {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}

/* Plus large que les autres boîtes : celle-ci porte une LISTE, pas un formulaire. Le padding est
   ici et non sur un <form> comme ailleurs — il n'y en a pas, parce qu'on enchaîne plusieurs gestes
   au lieu de valider une fois. */
.dlg-partage {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(96vw, 560px);
  padding: 20px;
  /* Même raison que `.dlg form`, et il faut le dire ici aussi : cette boîte-là n'a pas de <form>,
     donc la règle d'au-dessus ne l'atteint pas. Elle porte une LISTE — les invités d'un projet, ou
     les huit gestes d'une mise en place (#722) —, c'est-à-dire exactement ce qui grandit. */
  max-height: 86vh;
  overflow-y: auto;
}

.partage-liste {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Une grille, pour la même raison que les lignes de l'annuaire : les colonnes doivent rester
   alignées d'une ligne à l'autre, et c'est tout ce qu'on lui demande. */
.partage-ligne {
  display: grid;
  grid-template-columns: minmax(130px, 1.6fr) minmax(110px, 1.2fr) auto auto;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel2);
  font-size: 13px;
}

/* Un élément de grille garde `min-width: auto` : un <select> ne descend alors jamais sous son option
   la plus longue, et quatre « ops : selon le rôle (…) » côte à côte sortaient de la boîte (#1460).
   À zéro, c'est le champ qui cède à sa colonne, et non la ligne qui déborde. Le texte doit céder
   aussi : un courriel est un mot sans coupure, qui débordait de sa cellule et faisait défiler la
   boîte en large sur un téléphone (#1538) — il se coupe où il faut. */
.partage-ligne > * {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Le choix de l'équipe à attacher ou de la personne à inviter est la ligne la plus longue : il prend
   toute la première rangée, et les accès et le bouton reviennent à la ligne dessous (#1460). */
.partage-ligne > select[name="equipe"],
.partage-ligne > select[name="personne"] {
  grid-column: 1 / -1;
}

.partage-ligne select {
  width: 100%;
  text-overflow: ellipsis;
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--txt);
  font-family: inherit;
  font-size: 13px;
}

/* ─────────── les clés d'API d'une personne (#886) ─────────── */

/* La liste des clés partage la forme de celle du partage : ce sont les mêmes lignes en grille dans
   la même boîte, et deux déclarations divergeraient au premier changement d'espacement. */
.cles-liste,
.postes-liste {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Une clé fermée reste dans la liste — c'est ce qu'on vient relire —, mais elle ne doit pas se lire
   comme une clé qui ouvre encore. Le mot « fermée » est écrit à côté : la teinte seule ne dirait
   rien à qui ne distingue pas les deux. */
.cle-ligne.fermee {
  opacity: .62;
}

/* Où le poste range ses projets (#1646) : une rangée à elle, sous le nom et la clé. */
.poste-racine {
  grid-column: 1 / -1;
  font-size: 12px;
}

/* La bascule des fermées (#1369) se tient à gauche, à sa taille : étirée sur la largeur de la
   liste, elle se lirait comme une ligne de plus. */
.cles-bascule {
  align-self: flex-start;
}

.cle-emettre {
  grid-template-columns: 1fr auto;
}

/* La clé en clair. Le cadre est APPUYÉ, et c'est voulu : c'est le seul instant où elle est lisible,
   et une ligne de plus au milieu d'une liste se referme sans qu'on l'ait recopiée. */
.cle-revelee {
  padding: 12px;
  border: 1px solid var(--warn);
  border-radius: 10px;
  background: rgba(245, 158, 11, .1);
}

.cle-alerte {
  margin-bottom: 8px;
  color: var(--warn);
  font-size: 13px;
}

.cle-valeur {
  display: flex;
  align-items: center;
  gap: 8px;
}

.cle-valeur input {
  flex: 1;
  min-width: 0;
}

/* ─────────── vue Paramètres ─────────── */

/* Une carte pleine largeur, mais BORNÉE : un champ de chemin étalé sur un écran large se lit mal,
   et la ligne qu'on relit pour la vérifier est justement celle qu'on ne doit pas se tromper. */
.carte-large {
  max-width: 720px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
}

.carte-large form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 14px;
}

.champ {
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--muted);
  font-size: 12px;
}

/* Les CASES sont exclues, comme dans le défaut de #918 juste au-dessus : la police à chasse fixe et
   les 8 px de marge intérieure sont ceux d'un champ où l'on TAPE, et posés sur une case ils la
   déforment. Le défaut les excluait déjà ; cette règle-ci les rattrapait, et ça ne s'est vu que le
   jour où un formulaire de cette carte en a porté (#859). L'accent leur vient d'`accent-color`. */
.champ input:not([type="checkbox"]):not([type="radio"]) {
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-family: inherit;
  font-size: 14px;
}

.champ input.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Les cases d'une préférence en LISTE (#859) : les onglets affichés — et l'unique case d'une
   BASCULE (#858), qui se range de la même façon. En ligne et non empilées : elles sont courtes, et
   une colonne de quatre cases ferait une carte deux fois plus haute que ce qu'elle dit. */
.champ-cases {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
}

/* `.hint` est taillée pour une barre d'outils (`margin-left: auto`). Sous un champ, elle doit
   rester collée à gauche, sinon l'explication part à l'opposé du champ qu'elle explique. */
.champ .hint,
.carte-large > .hint {
  margin-left: 0;
}

.champ input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.carte-large .btn-porte {
  align-self: flex-start;
}

/* La PAGE des Paramètres (#1074) : un en-tête et son sommaire, puis trois sections titrées par leur
   portée. Les cartes restent bornées (cf. `.carte-large`) ; ce qui manquait, c'est l'AIR entre
   elles — elles s'empilaient bord à bord, et on ne voyait pas où finissait l'une. */
.reglages {
  display: flex;
  flex-direction: column;
  gap: 28px;
  max-width: 820px;
}

.reglages-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
}

.reglages-titre {
  margin: 0;
  color: var(--txt);
  font-size: 20px;
  font-weight: 600;
}

/* Les onglets des Paramètres (#1302) : des `btn-sm`, l'actif se lit par `.btn-sm.actif`. */
.reglages-onglets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.reglages-section {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Le sélecteur d'un onglet (#2039) : le poste, ou l'organisation, qu'on règle — les mêmes `btn-sm`
   que les onglets, qui passent à la ligne ; chaque bouton dit après son nom son carnet et son état.
   Ses panneaux se suivent comme les cartes de l'onglet. */
.reglages-selecteur {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.reglages-selecteur .btn-sm {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.selecteur-etat {
  color: var(--muted);
  font-size: 11px;
  font-weight: 400;
}

.selecteur-panneau {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.carte-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 12px;
}

.carte-tete .section-title {
  margin: 0;
  color: var(--txt);
}

.carte-tete + .hint {
  display: block;
  margin: 6px 0 0;
}

/* Ce qu'une carte EXIGE (la lecture seule d'un non-administrateur) : dit en tête, et non au pied
   d'un formulaire qu'on aurait déjà essayé de remplir. */
.carte-marque {
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 11px;
}

/* ─────────── la page « Mise en place » (#1994, js/mise-en-place.js) ───────────
   Une étape par ligne : son numéro, ce qu'elle dit, et son geste à droite — la forme d'une ligne de
   réglage, avec un rang. Celle qu'on peut faire MAINTENANT porte un liseré : c'est la seule chose
   que la page a à faire trouver. */
.mise-en-place,
.mise-carnet {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.mise-carnet > h3,
.mise-profil > h4 {
  margin: 0;
  color: var(--txt);
  font-size: 14px;
}

.mise-resume {
  margin: 0;
  color: var(--txt2);
}

.mise-resume.faite {
  color: var(--encre-ok);
}

/* L'encart « Me faire aider » (#2040), en tête de chaque onglet des Paramètres : le robot, ce qu'il
   propose, et le geste à droite — qui passe dessous quand la place manque. De la largeur des cartes
   qu'il précède. */
.encart-aide {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  max-width: 720px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel);
}

.encart-aide[hidden] {
  display: none;
}

.encart-aide-icone {
  display: flex;
  color: var(--accent);
}

.encart-aide-icone .icone {
  width: 28px;
  height: 28px;
}

.encart-aide-dit {
  display: flex;
  flex: 1 1 260px;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.encart-aide-dit .hint {
  margin: 0;
}

.encart-aide-gestes {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Le repli : une commande longue, qui se lit et se copie sans élargir l'encart. */
.encart-aide-repli {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  margin-top: 4px;
  color: var(--txt2);
  font-size: 12px;
}

.encart-aide-repli code {
  align-self: stretch;
  max-height: 5.6em;
  padding: 6px 8px;
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: all;
}

.mise-profil > .hint {
  display: block;
  margin: 2px 0 0;
}

.mise-etapes {
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}

.mise-etape {
  display: grid;
  grid-template-columns: 2.2em minmax(0, 1fr) minmax(0, 17em);
  align-items: start;
  gap: 6px 14px;
  padding: 12px 10px;
  border-top: 1px solid var(--line);
  border-left: 3px solid transparent;
}

.mise-etape:first-child {
  border-top: 0;
}

.mise-etape.suivante {
  border-left-color: var(--accent);
  background: var(--accent-soft);
}

.mise-numero {
  color: var(--muted);
  font-weight: 700;
  text-align: center;
}

.mise-etape[data-etat="fait"] .mise-numero {
  color: var(--encre-ok);
}

.mise-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
}

.mise-phrase,
.mise-constat,
.mise-avis {
  margin: 4px 0 0;
  font-size: 12px;
  line-height: 1.45;
}

.mise-phrase {
  color: var(--muted2);
}

.mise-constat {
  color: var(--txt2);
}

.mise-avis {
  color: var(--encre-warn);
}

.mise-geste {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.mise-geste .btn-sm {
  text-align: left;
  text-decoration: none;
  white-space: normal;
}

.mise-geste small {
  color: var(--muted2);
  font-size: 11px;
  line-height: 1.4;
}

.pastille.due {
  border-color: var(--accent);
  color: var(--accent);
}

.pastille.attente {
  border-style: dashed;
}

/* Dans la vue Projets d'une base vide : la carte prend la place du texte, à la largeur d'une page. */
.mise-accueil {
  margin: 24px auto 0;
}

@media (max-width: 700px) {
  .mise-etape {
    grid-template-columns: 2.2em minmax(0, 1fr);
  }

  .mise-geste {
    grid-column: 2;
  }
}

/* « Mes postes » (#1995) : les cartes des postes se suivent comme celles d'un onglet, et la zone
   vide — tout est dans la carte du poste où l'on est — ne laisse pas un double écart. */
.mes-postes {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.mes-postes:empty {
  display: none;
}

/* Un BLOC d'une carte de poste : ce qui était une carte (mise à jour, journaux, lancer), sous le
   poste qu'il règle. */
.carte-bloc {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.carte-large form.reglages-liste {
  gap: 0;
}

.reglages-groupe {
  margin: 16px 0 2px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
}

.reglages-groupe:first-child {
  margin-top: 0;
}

/* UNE ligne de réglage : ce qu'il est à gauche, le geste à droite, un filet entre deux lignes.
   Le geste est BORNÉ (#2066) : en `auto`, une phrase longue — la clé d'un poste et son libellé —
   prenait toute la ligne, et l'explication, qui peut tomber à zéro, s'écrasait à un mot par ligne.
   Au-delà de sa part, c'est le geste qui se replie. */
.reglage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) fit-content(60%);
  align-items: center;
  gap: 8px 20px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}

.reglages-groupe + .reglage,
.reglages-liste > .reglage:first-child {
  border-top: 0;
}

.reglage.large {
  grid-template-columns: minmax(0, 1fr);
}

.reglage-dit {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.reglage-nom {
  color: var(--txt);
  font-size: 14px;
}

.reglage-aide {
  color: var(--muted2);
  font-size: 12px;
  line-height: 1.45;
}

.reglage-geste {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 6px 12px;
}

.reglage.large .reglage-geste {
  justify-content: flex-start;
}

.reglage-geste .hint {
  margin-left: 0;
}

.reglage-geste select {
  min-width: 150px;
}

/* Le même habit que les champs des autres cartes (`.champ`), sur toute la largeur : un chemin se
   relit mal dans une demi-colonne. Les cases en sont exclues, pour la raison écrite plus haut. */
.reglage-geste input:not([type="checkbox"]):not([type="radio"]) {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-family: inherit;
  font-size: 14px;
}

.reglage-geste input.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.reglage-geste input:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.reglages-liste > .btn-porte {
  margin-top: 12px;
}

/* Étroit : le geste passe sous ce qu'il règle, aligné à gauche comme lui. */
@media (max-width: 600px) {
  .reglage {
    grid-template-columns: minmax(0, 1fr);
  }

  .reglage-geste {
    justify-content: flex-start;
  }
}

/* ─────────── la fiche d'un ticket (#777) ───────────
 *
 * Trois colonnes (#992) : ce que le ticket EST (les faits), ce qu'on y LIT (le contenu), ce qui
 * s'y est DIT (la discussion). La discussion défile seule et garde sa hauteur, sinon une frise de
 * cinquante lignes pousse le reste hors de l'écran et il faut remonter pour relire le titre. */

.fiche-barre {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/* Les gestes du ticket, à DROITE de la barre du haut (#948) : l'identité à gauche, ce qu'on peut en
   faire à l'autre bout. `margin-left: auto` et non une colonne — la barre se replie sur un écran
   étroit, et les gestes passent alors simplement à la ligne. */
.fiche-gestes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-left: auto;
}

/* Le menu « ↔ État… » (#955) a la taille d'un `.btn-sm`, ses voisins : le défaut de `:where(select)`
   l'habille, il manque la même hauteur, sans quoi la barre sautille entre un bouton et le menu. */
.fiche-etat {
  padding: 3px 6px;
  border-radius: 7px;
  font-size: 12px;
}

/* ▶ ▶▶ de l'aperçu (#955) : ceux de la carte, sans le `margin-left: auto` qui les pousse au bout de
   la ligne du projet — ici ils ouvrent la rangée des gestes, et passeraient sinon seuls à droite. */
.fiche-gestes .t-lancer {
  margin-left: 0;
}

/* La suite des gestes et son « … » (#1606) : sur un écran large, la suite s'efface (`contents`) et
   ses boutons sont ceux de la barre, comme avant ; le « … » ne sert qu'au téléphone. */
.gestes-suite {
  display: contents;
}

.gestes-plus {
  display: none;
}

/* Au téléphone, la suite est repliée, et le « … » la déplie en une rangée à elle : ↔ État… et ▶ ▶▶
   restent seuls en vue, et le titre arrive sous une rangée au lieu de cinq. */
@media (max-width: 600px) {
  .gestes-plus {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .gestes-plus[aria-expanded="true"] {
    background: var(--panel2);
  }

  .gestes-suite {
    display: none;
  }

  .fiche-gestes.deplie .gestes-suite {
    display: flex;
    flex-wrap: wrap;
    flex-basis: 100%;
    gap: 6px;
  }
}

.fiche-titre {
  margin: 10px 0 14px;
  font-size: 20px;
}

/* À deux colonnes, les neuf blocs s'empilaient dans 380 px pendant que la frise prenait toute la
   largeur restante (#992). Les faits sont des lignes courtes : colonne étroite. Le contenu et la
   discussion se partagent le reste à PARTS ÉGALES — la frise ne l'emporte plus sur ce qu'on lit. */
.fiche-grille {
  display: grid;
  grid-template-columns: minmax(260px, 340px) minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas: "faits contenu canal";
  gap: 14px;
  align-items: start;
}

.fiche-faits {
  grid-area: faits;
}

.fiche-contenu {
  grid-area: contenu;
}

.fiche-droite {
  grid-area: canal;
}

/* Trois colonnes jusqu'à 1100 px, et pas 1400 (#1035). Un portable à 1920 px affiché à 150 % a
   1280 px CSS : le repli à deux colonnes y remettait faits et contenu dans 420 px à gauche, et la
   discussion prenait le double — le défaut de #992, revenu. Or à cette largeur, trois colonnes
   donnent au contenu AUTANT que ce repli (~426 px), sans l'empiler sous les faits ; à 1100 px il
   leur reste encore ~336 px chacun. En dessous, les faits gardent seuls la colonne étroite, et le
   contenu puis la discussion se suivent à droite, en pleine largeur : aucune colonne ne porte plus
   deux blocs à la largeur d'une carte. Les faits couvrent les deux rangées, et c'est la seconde
   qui prend la hauteur qui reste — sinon une longue colonne de faits creuserait un vide ENTRE le
   contenu et la discussion. `verifier_front` relit ces seuils ici même. */
@media (max-width: 1100px) {
  .fiche-grille {
    grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas: "faits contenu" "faits canal";
  }
}

/* Une seule colonne quand l'écran ne peut pas en tenir deux : à 300 px de faits, la conversation
   n'aurait plus la largeur d'une phrase. */
@media (max-width: 900px) {
  .fiche-grille {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    grid-template-areas: "faits" "contenu" "canal";
  }
}

.fiche-bloc {
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  margin-bottom: 12px;
}

/* Le texte d'une description vient d'une saisie : il porte ses retours à la ligne, et les perdre
   collerait en un seul pavé ce que quelqu'un avait pris la peine d'aérer. `pre-wrap` et non `pre` —
   les lignes longues doivent quand même se replier dans la colonne. */
.fiche-desc,
.fil-txt {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.5;
}

.fiche-champ {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 0;
  border-top: 1px solid var(--line);
  font-size: 12px;
}

.fiche-champ > span:first-child {
  color: var(--muted2);
}

.fiche-champ .qui {
  margin-top: 0;
}

/* L'effort se choisit SUR la fiche (#965), et le modèle aussi (#1289). Le défaut de
   `:where(select)` habille déjà la liste ; ce qui manque est la TAILLE : une ligne de fiche fait
   12 px, et un champ de formulaire posé tel quel au milieu la fait sauter de dix pixels. */
.fiche-choix {
  padding: 1px 6px;
  border-radius: 6px;
  font-size: 11px;
}

/* Une référence de fiche (#725). Une grille et non un bloc de texte : l'icône dit la sorte du bout
   de l'œil, la cible prend la place qu'il lui faut, et la croix reste à droite quelle que soit la
   longueur du chemin — un chemin Windows fait cent caractères et n'a nulle part où se couper. */
.fiche-ref {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 3px 0;
  color: var(--muted);
  font-size: 12px;
  overflow-wrap: anywhere;
}

.fiche-ref .ref-icone {
  flex: none;
}

/* `0 1 auto` et non `1 1 auto` : la cible prend ce qu'il lui faut et rétrécit si elle déborde, sans
   occuper toute la ligne — sans quoi le libellé se retrouverait plaqué à droite, loin de ce qu'il
   décrit. C'est la marge automatique de `.pl-x` qui pousse la croix au bord. */
.fiche-ref .ref-corps {
  flex: 0 1 auto;
  min-width: 0;
}

/* Un ticket référencé réutilise la carte d'une dépendance (`fiche-lien`), qui porte déjà sa marge
   du haut : sur une ligne de référence, elle la décalerait de son icône. */
.fiche-ref .fiche-lien {
  margin-top: 0;
}

.fiche-ref .ref-cible {
  color: var(--txt);
}

.fiche-ref a.ref-cible {
  color: var(--accent);
}

.fiche-ref .ref-libelle {
  flex: none;
  color: var(--muted2);
}

/* Elle ne pousse pas la croix : c'est `.pl-x` qui prend la marge automatique, et deux `margin-left:
   auto` sur la même ligne collent le libellé au milieu. */
.fiche-ref .pl-x {
  align-self: center;
}

/* ─────────── une pièce jointe (#849) ───────────
 *
 * La même grille qu'une référence, et volontairement : les deux répondent à la même question — ce
 * qu'il faut regarder pour traiter le ticket —, et deux mises en forme différentes feraient lire
 * deux natures de choses là où il n'y en a qu'une.
 *
 * Ce qui change est la VIGNETTE, et c'est ce qui vaut la peine : une capture reconnue d'un coup
 * d'œil évite d'ouvrir trois fichiers pour retrouver le bon. `align-items: center` et non
 * `baseline` pour cette raison-là — une image n'a pas de ligne de base, et le nom se retrouverait
 * calé sur son bas. */
.fiche-pj {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  color: var(--muted);
  font-size: 12px;
  overflow-wrap: anywhere;
}

.fiche-pj .pj-icone {
  flex: none;
}

/* Bornée en HAUTEUR et non en largeur : une capture d'écran est large, un logo est carré, et une
   borne sur la largeur ferait de la première une bande de deux pixels de haut. */
.fiche-pj .pj-vignette {
  flex: none;
  display: block;
  line-height: 0;
}

.fiche-pj .pj-vignette img {
  height: 40px;
  max-width: 120px;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: 4px;
}

.fiche-pj .pj-nom {
  flex: 0 1 auto;
  min-width: 0;
  color: var(--accent);
}

/* Celle d'un ticket HÉBERGÉ (#1808) est un bouton — l'hôte l'ouvre, il n'y a pas d'adresse à suivre —
   dessiné comme le lien de celles d'ici. */
.fiche-pj button.pj-nom {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* Un `<label>` qui porte un champ fichier caché : c'est le seul moyen d'avoir un bouton du dessin
   de l'app plutôt que le « Parcourir… » du système, qui ne ressemble à rien de cet écran. Le
   curseur le dit, sans quoi rien n'annonce que c'est cliquable. */
.pj-choisir {
  cursor: pointer;
}

/* Sur une ligne de frise, la pièce jointe est en DESSOUS du texte qu'elle illustre, décalée de la
   même façon que lui — collée à gauche, elle se lirait comme une ligne à part. */
.fil-ligne .fiche-pj {
  padding-left: 2px;
}

/* ─────────── ce qu'on écrit depuis la fiche (#845) ───────────
 *
 * Un geste interdit reste VISIBLE et grisé : le cacher se lirait « ça ne se fait pas ici », et on
 * irait le chercher ailleurs. Le curseur le dit aussi — un bouton qui a l'air cliquable et ne
 * répond pas est pire qu'un bouton éteint. */
.pl-actions .btn-sm:disabled,
.fiche-gestes .btn-sm:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.pl-actions .btn-sm:disabled:hover,
.fiche-gestes .btn-sm:disabled:hover {
  border-color: var(--line);
}

/* La RAISON du refus, en toutes lettres sous la barre. Un `title` sur un bouton désactivé ne
   s'ouvre pas partout : une raison qu'on ne peut pas lire ne vaut pas mieux qu'une absence, et
   c'est précisément ce que ce ticket existe pour retirer. */
.fiche-refus {
  margin-top: 8px;
  color: var(--muted2);
  font-size: 12px;
  line-height: 1.45;
}

/* Sous la barre du haut, la raison précède le titre du ticket et ne doit pas s'y coller (#948). */
.fiche-refus + .fiche-titre {
  margin-top: 8px;
}

/* ─────────── la clôture, en tête de fiche (#937) ───────────
 *
 * Un bandeau et non un bloc de la colonne de gauche : sur un ticket terminé, « ce qui a été fait »
 * est ce qu'on vient lire, et le mettre en huitième position reviendrait à ne pas l'afficher. Le
 * filet vert est celui de « Clôturer », le même geste sous une autre forme.
 *
 * La variante PROPOSÉE (passage en test, pas encore clos) porte le ton d'attente et non celui de la
 * réussite : elle dit un texte qui pourra encore changer. */
.fiche-cloture {
  margin: 10px 0 14px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--ok);
  border-radius: var(--radius);
  background: var(--panel);
}

.fiche-cloture.proposee {
  border-left-color: var(--muted2);
}

.fiche-cloture-tete {
  margin-bottom: 6px;
  color: var(--muted2);
  font-size: 12px;
}

/* Le commentaire de clôture vient d'une saisie et porte ses retours à la ligne, comme une
   description : les perdre collerait en un pavé les cinq points qu'on avait pris soin de séparer. */
.fiche-cloture-mot {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  line-height: 1.5;
}

/* ─────────── la fiche RAPIDE, en fenêtre au-dessus du tableau (#924) ───────────
 *
 * Elle réemploie le vocabulaire de la fiche — `fiche-barre`, `fiche-titre`, `fiche-bloc`,
 * `fiche-desc` —, et c'est voulu : le même ticket doit se lire pareil des deux côtés. Ce qui suit
 * n'ajuste donc que ce qu'une BOÎTE change, et rien du dessin lui-même.
 *
 * Plus large que `.dlg-partage`, comme la boîte d'un ticket (`.dlg-large`) et pour la même raison :
 * une description fait dix lignes, et à 560 px elle en fait vingt.
 *
 * Elle n'est pas MODALE (#1006) : ouverte par `show()`, elle n'est plus dans la couche du dessus ni
 * centrée par le navigateur, et elle n'a pas de voile. Elle se range donc en PANNEAU fixe à droite,
 * sous la barre du haut : les colonnes de gauche restent à portée du glisser, et le défilement du
 * tableau ne l'emporte pas. Sous la zone « Clôturer » (z-index 40), qui n'apparaît qu'avec un
 * glisser et doit rester une cible, au-dessus de la barre (30). */
.dlg-apercu {
  position: fixed;
  top: calc(var(--haut-barre) + var(--barre-fenetre, 0px) + 12px);
  right: 16px;
  left: auto;
  z-index: 35;
  width: min(94vw, 640px);
  max-height: calc(var(--haut-ecran) - var(--barre-fenetre, 0px) - var(--haut-barre) - 28px);
  margin: 0;
  box-shadow: var(--ombre-flottante);
}

.apercu {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Les blocs sont espacés par la colonne ci-dessus : leur marge de fiche s'ajouterait à l'écart, et
   le dernier en laisserait un sous lui, juste au-dessus du bouton « Fermer ». */
.apercu .fiche-bloc {
  margin-bottom: 0;
}

/* Le titre ne porte pas les marges qu'il a en pleine page : là-bas il ouvre un écran, ici il est
   une ligne parmi d'autres dans une colonne qui espace déjà. Un cran plus petit, aussi — une boîte
   n'a pas la largeur d'une page, et 20 px y passeraient sur trois lignes. */
.apercu .fiche-titre {
  margin: 0;
  font-size: 17px;
}

/* La clôture non plus : même raison, et c'est la colonne qui l'écarte de son titre. */
.apercu .fiche-cloture {
  margin: 0;
}

/* Au téléphone (#1606), la boîte prend la largeur de l'écran à 8 px de chaque bord : calée à droite
   avec 94vw, elle sortait de 3 px à gauche. Et le TITRE passe avant la barre : c'est ce qu'on ouvre
   un aperçu pour lire, et il arrivait à mi-écran sous les gestes. */
@media (max-width: 600px) {
  .dlg-apercu {
    left: 8px;
    right: 8px;
    width: auto;
    max-width: none;
  }

  .apercu > .fiche-titre {
    order: -1;
  }
}

/* ─────────── ce qui a été fait : les commits, sur la fiche (#848) ───────────
 *
 * Trois lectures qui s'emboîtent — le commit, ses fichiers, l'historique d'un chemin —, et c'est
 * l'INDENTATION qui dit laquelle on regarde : sans elle, l'historique d'un fichier se lirait comme
 * la suite des commits du ticket, c'est-à-dire comme du travail qui n'est pas le sien. */
.fiche-commit {
  padding: 5px 0;
  border-top: 1px solid var(--line);
  font-size: 12px;
}

.commit-tete {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

/* L'identifiant abrégé est le bouton qui déplie : il a la forme d'un identifiant (`mono`) et le
   comportement d'un bouton, sans en avoir le cadre — une barre d'outils sur chaque ligne de commit
   noierait le sujet, qui est ce qu'on vient lire. */
.commit-ref {
  flex: none;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
}

.commit-sujet {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--txt);
  overflow-wrap: anywhere;
}

.commit-lien {
  flex: none;
  color: var(--muted);
  text-decoration: none;
}

.commit-dits {
  font-size: 11px;
}

.commit-fichiers,
.commit-suite {
  margin-left: 14px;
  padding-left: 8px;
  border-left: 1px solid var(--line);
}

.commit-fichier {
  padding: 3px 0;
}

/* Le chemin d'un fichier se coupe n'importe où : « app/static/js/app.js » tient, un chemin de
   sous-dossier profond non — et un bloc de fiche fait la moitié d'un écran. */
.commit-fichier [data-suivre] {
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  text-align: left;
  overflow-wrap: anywhere;
  cursor: pointer;
}

/* Un collaborateur et sa croix tiennent ensemble : sans ça, la croix se replie seule sur la ligne
   suivante dès que trois noms se suivent, et on ne sait plus qui elle retire. */
.fiche-collab {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  white-space: nowrap;
}

/* La croix d'un collaborateur et celle d'une dépendance ne poussent PAS : `margin-left: auto`
   pousserait le nom au milieu de sa ligne, comme sur une référence. */
.fiche-collab .pl-x,
.fiche-dep .pl-x {
  margin-left: 4px;
}

.fiche-dep {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Le lien qu'on a lâché (#956) : la dépendance est toujours là, elle ne pilote plus l'état. Le ton
   de l'alerte et non celui d'une dépendance ordinaire — c'est une CONTRADICTION entre ce qu'on lit
   (« en attente de #12 ») et ce que fait l'app, et c'est elle qui fait croire à une panne. */
.dep-lache {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 7px 9px;
  border: 1px solid rgba(245, 158, 11, .45);
  border-radius: 8px;
  color: var(--encre-warn);
  font-size: 12px;
}

.fiche-dep .fiche-lien {
  flex: 1;
  min-width: 0;
}

.fiche-lien {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin-top: 6px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.fiche-lien:hover {
  border-color: var(--accent);
}

/* La pastille de projet d'une ligne de ticket ne se laisse pas écraser par un titre long : c'est
   elle qui dit qu'une dépendance traverse un projet, et tronquée elle ne dit plus rien. */
.fiche-lien .proj-chip {
  flex: 0 0 auto;
}

/* ─────────── la pioche de tickets (#956) ─────────── */

.pioche-q {
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-family: inherit;
  font-size: 14px;
}

/* C'est la LISTE qui défile, et pas la boîte : la recherche doit rester sous les yeux pendant
   qu'on parcourt, sinon on remonte pour affiner et on a perdu sa place. Même raison que le texte
   d'un skill, qui borne sa hauteur plutôt que de laisser sa boîte grandir. */
.pioche-liste {
  display: flex;
  flex-direction: column;
  max-height: 50vh;
  overflow-y: auto;
}

/* ─────────── le sélecteur de canal, et le fil ─────────── */

.canal-tete {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.canal-tete select {
  flex: 1;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font: inherit;
  font-size: 13px;
}

.canal-entete {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  margin-bottom: 10px;
}

.canal-parts {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin: 6px 0;
}

/* Le ticket qu'on regarde, parmi les participants : il se distingue sans avoir à lire le numéro. */
.canal-parts .tag.soi {
  border-color: var(--accent);
  color: var(--accent);
}

.canal-entete .hint {
  margin-left: 0;
}

/* La zone d'écriture est AU-DESSUS du fil, hors de lui (#2004) : c'est le fil seul qui défile, et
   sa hauteur cède la place qu'elle prend — 140 px de plus que ce qui le précédait déjà. Le plancher
   tient pour un écran que le clavier a réduit : sans lui, le fil n'aurait plus la hauteur d'un mot. */
.fil {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: max(180px, calc(var(--haut-ecran) - var(--barre-fenetre, 0px) - 400px));
  overflow-y: auto;
  padding-right: 4px;
}

.fil-ligne {
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: 10px;
  background: var(--panel);
  font-size: 13px;
}

.fil-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  margin-bottom: 5px;
  font-size: 11px;
}

.fil-tete .qui {
  margin-top: 0;
}

/* ─────────── ce que « origine » et « motif » décident (#704) ───────────
 *
 * La COULEUR se prend sur l'origine, le REPLI sur le motif, et l'ALIGNEMENT sur le sens du canal.
 * Les trois descendent en CLASSES depuis les données : le JavaScript n'a aucun de ces mots écrit
 * chez lui, et il n'a donc rien à apprendre le jour où le vocabulaire s'enrichit — une valeur
 * inconnue retombe simplement sur la mise en forme neutre ci-dessus.
 *
 * `frise-org-inconnu` n'est pas une faute : une base d'avant #704 que la reprise n'a pas traversée
 * rend « origine » à null, et « on ne sait pas » n'est pas « personne ». */
.frise-org-personne {
  border-left-color: var(--accent);
}

.frise-org-session {
  border-left-color: var(--info);
}

.frise-org-systeme {
  border-left-color: var(--muted2);
  color: var(--muted);
}

/* Une question sans réponse : c'est elle qui empêche de clore, et elle doit se voir de loin. */
.frise-attente {
  border-color: var(--warn);
}

.attente {
  margin-top: 5px;
  color: var(--warn);
  font-size: 11px;
}

/* « C'est celle-ci que tu cherchais » : la ligne qu'un extrait de recherche a fait ouvrir (#827),
   et la carte de projet où « Voir le projet » vient de mener (#967). Un HALO et non une couleur de
   bordure : l'origine, le camp et l'attente peignent déjà celle-là, et la leur dit ce que la ligne
   EST — celui-ci dit seulement qu'on a été mené ici. Les deux doivent se lire ensemble.
   Une seule écriture du halo, à son deuxième porteur : deux visées qui se répondent à l'écran et
   divergent dans cette feuille se remarqueraient tout de suite. */
.fil-ligne.vise,
.carte.vise,
.lp-ligne.vise {
  box-shadow: 0 0 0 2px var(--accent);
}

/* Le fond teinté ne suit PAS sur une carte de projet : sa bande et ses jauges portent déjà la
   couleur du projet, et un second aplat d'accent par-dessus ferait deux couleurs qui se disputent
   la même carte. Le halo suffit — il désigne, il ne repeint pas. */
.fil-ligne.vise {
  background: var(--accent-soft);
}

/* Les bulles d'une conversation : ce ticket-ci d'un côté, ceux d'en face de l'autre. Le camp se lit
   à la POSITION, avant même d'avoir lu le numéro. Aucun des deux sur toute la largeur — un fil où
   tout est aligné pareil oblige à déchiffrer chaque en-tête pour savoir qui parle. */
.frise-envoye,
.frise-recu {
  max-width: 85%;
}

.frise-envoye {
  align-self: flex-end;
  background: var(--panel2);
}

.frise-recu {
  align-self: flex-start;
}

/* Les deux motifs qui sont LONGS par nature : un commentaire de clôture et un arbitrage font
   couramment plusieurs milliers de caractères. Déroulés, deux d'entre eux remplissent la colonne et
   la conversation n'a plus de forme — on voit donc la suite des échanges d'abord, le détail à la
   demande. Le repli est ici et pas en JavaScript : rien n'est coupé dans le DOM, et la recherche du
   navigateur (Ctrl+F) retrouve le texte replié. */
.frise-motif-cloture .fil-txt,
.frise-motif-arbitrage .fil-txt {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

.frise-motif-cloture .fil-txt.deplie,
.frise-motif-arbitrage .fil-txt.deplie {
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
}

.fil-plus {
  margin-top: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

.q-badge {
  padding: 2px 8px;
  border: 1px solid var(--warn);
  border-radius: 999px;
  color: var(--warn);
  font-size: 11px;
}

/* « Attend la personne » (#2006) : REMPLI, là où les autres comptes de questions ne sont que bordés —
   c'est le seul des trois qui demande quelque chose à qui regarde l'écran. */
.q-badge.personne {
  background: rgba(245, 158, 11, .16);
  color: var(--encre-warn);
  font-weight: 600;
}

/* ─────────── écrire sur un ticket, depuis sa fiche (#768) ───────────
 *
 * Deux gestes, et deux seulement : répondre à une question qui attend quelqu'un, et demander un
 * lancement. Ils vivent dans la fiche et pas dans un dialogue — on répond en relisant ce qui est
 * écrit juste au-dessus, et un dialogue le cacherait. */

.fil-ecrire {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel2);
}

/* En tête de la colonne de discussion, c'est du fil qu'elle s'écarte, plus de ce qui la précède. */
.fiche-droite > .fil-ecrire {
  margin: 0 0 10px;
}

.fil-ecrire textarea {
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--txt);
  font: inherit;
  resize: vertical;
}

/* Le pied porte trois choses depuis #941 : le trombone, ce qui attend d'être joint, et l'envoi.
   C'est la LISTE des pièces en attente qui prend la place restante et qui la cède — six noms de
   fichiers pousseraient sinon le bouton « Commenter » hors de la zone. */
.fil-ecrire-pied {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Il POUSSE le bouton à droite même vide, ce qui garde au pied la disposition qu'il avait avant
   d'avoir trois occupants. */
.fil-ecrire-pied [data-pj-choisi] {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

.fil-ecrire-pied .pj-choisir,
.fil-ecrire-pied button[type=submit] {
  flex: none;
}

/* Les trois gestes de la zone, dits une fois, en petit : un raccourci que rien n'annonce n'existe
   pour personne, et celui qui le connaît ne relit pas cette ligne. */
.fil-ecrire > .hint {
  margin: 0;
}

/* ─────────── déposer des fichiers (#941) ───────────
 *
 * Le MÊME état pour les deux zones — le bloc des pièces jointes et la zone d'écriture —, parce que
 * c'est le même geste : un liseré qui change de couleur selon l'endroit ferait chercher une
 * différence qui n'existe pas. En `outline` et non en `border` : une bordure qui apparaît au survol
 * décale le contenu de deux pixels, et la cible bouge sous le fichier qu'on tient.
 *
 * C'est l'accent du produit, comme la zone « Clôturer » porte le vert de ce qu'elle fait. */
.depot-pj {
  outline: 2px dashed var(--accent);
  outline-offset: 3px;
}

/* LE cadre où l'on joint (#951) : un pointillé qui se lit comme l'endroit où l'on dépose, avant
   même d'avoir lu son texte — c'est la forme d'Artis Tickets, et ce qui manquait pour qu'on le
   trouve. Tout le cadre est cliquable (c'est un <label>), le mot « choisir » le dit en accent.
   `.dlg .zone-pj` parce que `.dlg label` en ferait sinon une colonne, en petites capitales. */
.zone-pj,
.dlg .zone-pj {
  display: block;
  margin-top: 8px;
  padding: 10px 12px;
  border: 2px dashed var(--line);
  border-radius: 9px;
  color: var(--muted);
  font-size: 12.5px;
  text-align: center;
  text-transform: none;
  letter-spacing: normal;
  cursor: pointer;
}

.zone-pj:hover {
  border-color: var(--accent);
}

.zone-pj-choisir {
  color: var(--accent);
  text-decoration: underline;
}

/* Le bloc « Pièces jointes » de la boîte « Nouveau ticket » : l'intitulé comme ceux des champs
   (`.dlg label`), puis le cadre et ce qui attend d'être joint. */
.dlg-pj {
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--muted);
  font-size: 12px;
}

.dlg-pj > span:first-child {
  letter-spacing: .4px;
  text-transform: uppercase;
}

.dlg-pj .zone-pj {
  margin-top: 0;
}

.fiche-lancer {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 8px;
}

/* Les deux boutons NOMMÉS de la fiche (#952) : c'est la liste des machines qui cède la largeur, pas
   « ▶▶ Autonome », qui passerait sinon sur deux lignes à côté d'un « ▶ Supervisé » qui tient sur une. */
.fiche-lancer .btn-sm {
  flex: none;
  white-space: nowrap;
}

.fiche-lancer select {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font: inherit;
}

/* Le compte Claude du lancement (#1399), sous la machine : même liste, même largeur. Son libellé
   se lit à l'horizontale, comme la barre au-dessus. */
.fiche-compte label {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 8px;
  font-size: 12px;
  color: var(--muted2);
}

.fiche-compte select {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font: inherit;
}

/* D'où vient le compte que la règle donne (#2025), sous sa liste : une phrase, pas un paragraphe. */
.fiche-compte p {
  margin: 6px 0 0;
  font-size: 12px;
}

/* « Mon compte Claude ici » (#2024) : la même liste, posée dans une carte des Paramètres, sur une
   carte d'équipe ou dans la boîte d'un projet. */
.compte-ici label {
  margin-top: 0;
}

.groupe .compte-ici {
  margin: 8px 0;
}

/* ─────────── créer un projet, et poser son dépôt (#722) ─────────── */

/* Une case à cocher se lit à l'HORIZONTALE, contrairement aux autres champs de boîte : la question
   est dans l'étiquette, et l'empiler au-dessus d'une case de 13 pixels laisserait une ligne vide au
   milieu du formulaire. `.dlg label` est en colonne — cette règle la renverse pour ce seul cas, et
   se dit donc DANS `.dlg` (#2066) : seule, elle pesait moins que celle qu'elle renverse, et la case
   se centrait au-dessus de son libellé. Le <div> qui range des boutons radio suit la même règle. */
.dlg .dlg-case,
.dlg .dlg-case label {
  flex-direction: row;
  gap: 8px;
  align-items: center;
  color: var(--txt);
  font-size: 13px;
}

.dlg .dlg-case input {
  width: auto;
  accent-color: var(--accent);
}

/* Les TEMPS d'une boîte qui se remplit en ordre (#1708, « Nouvelle organisation ») : un titre par
   temps, sans le cadre que le navigateur donne à un <fieldset>. Imbriqué, il ne sert qu'à désactiver
   d'un coup les champs du choix écarté : il n'ajoute alors rien à voir. */
.dlg-temps {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.dlg-temps legend {
  padding: 0 0 4px;
  color: var(--txt);
  font-size: 13px;
  font-weight: 600;
}

/* La COULEUR d'un projet (#1078) : la couleur du thème, un filet, la palette, et le sélecteur libre
   en dernier, dessiné comme une pastille de plus. Un <div> et non un <label> : un libellé qui
   enveloppe vingt boutons enverrait chaque clic sur le premier d'entre eux. La pastille retenue se
   lit à son anneau, dans la teinte du texte — l'accent disparaîtrait autour de la pastille or. */
.champ-couleur,
.champ-carnet {
  display: flex;
  flex-direction: column;
  gap: 5px;
  color: var(--muted);
  font-size: 12px;
}

.palette-couleurs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.pastille-couleur,
.palette-couleurs input[type="color"] {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 0 0 1px var(--line);
}

.pastille-couleur[aria-pressed="true"] {
  border-color: var(--panel2);
  box-shadow: 0 0 0 2px var(--txt);
}

.palette-couleurs input[type="color"] {
  background: var(--panel2);
}

.palette-couleurs input[type="color"]::-webkit-color-swatch-wrapper {
  padding: 0;
}

.palette-couleurs input[type="color"]::-webkit-color-swatch {
  border: none;
  border-radius: 50%;
}

.palette-couleurs input[type="color"]::-moz-color-swatch {
  border: none;
  border-radius: 50%;
}

.palette-separe {
  width: 1px;
  height: 20px;
  background: var(--line);
}

/* La marche à suivre du dépôt. Un bloc par geste, et la commande DÉTACHÉE du texte : c'est elle
   qu'on vient copier, et elle doit se distinguer du premier coup d'œil de la phrase qui l'explique. */
.mep-entete,
.mep-gestes {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.geste-titre {
  color: var(--txt);
  font-size: 13px;
  font-weight: 600;
}

/* `pre` et pas une div : les commandes d'un geste sont plusieurs LIGNES, et l'ordre y compte. Le
   défilement horizontal plutôt qu'un retour à la ligne — une commande coupée en deux se recopie
   fausse, et c'est exactement ce que cette boîte existe pour éviter. */
.geste pre {
  margin: 6px 0;
  padding: 8px 10px;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-size: 12.5px;
  white-space: pre;
  user-select: all;
}

/* Le texte entier d'un skill, lu depuis une pastille (#966). Il RETOURNE À LA LIGNE, contrairement
   à la commande d'un geste : c'est de la prose Markdown, pas une ligne à recopier — la couper ne
   fait rien de faux, et la faire défiler horizontalement la rendrait illisible. La hauteur est
   bornée ici et non par la boîte : `.dlg-partage` défile déjà, et deux défilements imbriqués font
   perdre le chapeau dès qu'on descend. */
.skill-texte {
  max-height: 60vh;
  margin: 0;
  padding: 10px 12px;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  color: var(--txt);
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ─────────── la page « ? » (#860) ─────────── */

/* Une colonne de lecture : c'est du texte, et une ligne de 1 400 px ne se lit pas. */
.aide {
  max-width: 880px;
  margin: 0 auto;
}

.aide-tete h2 {
  margin: 0 0 8px;
  font-size: 20px;
}

.aide-tete p {
  margin: 0 0 18px;
  color: var(--txt2);
  line-height: 1.55;
}

.aide-bloc {
  line-height: 1.55;
}

.aide-bloc .section-title {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.aide-bloc p {
  margin: 0 0 8px;
  color: var(--txt2);
}

.aide-bloc ul,
.aide-bloc ol {
  margin: 0;
  padding-left: 20px;
  color: var(--txt2);
}

/* Une liste numérotée — un ordre qui compte, comme les rangs d'une règle — est suivie de ses notes. */
.aide-bloc ol {
  margin-bottom: 8px;
}

.aide-bloc li + li {
  margin-top: 6px;
}

/* Le lien « Ouvrir » d'une section : en minuscules, alors que le titre de section est en capitales —
   c'est un geste, pas une partie du titre. */
.aide-ouvrir {
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
}

.aide-mots {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 16px;
  margin: 0;
}

.aide-mots dt {
  color: var(--txt);
  font-weight: 600;
}

.aide-mots dd {
  margin: 0;
  color: var(--txt2);
}

/* UNE CAPTURE (#1090) : l'écran tel qu'il est, dans le thème qu'on lit. La page porte les deux
   fichiers et la feuille en cache un, comme pour la marque (#859) — et pour la même raison, c'est le
   thème qui choisit, pas `app.js`. Un clic l'ouvre en grand : dans une colonne de 880 px, une capture
   de 1 440 px se parcourt, elle ne se lit pas dans le détail. Elles se refont par
   `devtools/captures_aide.mjs`, jamais à la main. */
.aide-capture {
  margin: 4px 0 12px;
}

.aide-capture a {
  display: block;
}

.aide-capture img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
}

/* L'écran de téléphone garde sa largeur : étiré sur 880 px, il ne ressemblerait plus à rien. */
.aide-capture.etroite img {
  width: 300px;
  max-width: 100%;
}

.aide-capture figcaption {
  margin-top: 6px;
  font-size: 12px;
  color: var(--muted);
}

.aide-capture .en-clair {
  display: none;
}

[data-theme="clair"] .aide-capture .en-sombre {
  display: none;
}

[data-theme="clair"] .aide-capture .en-clair {
  display: block;
}

/* LES PARCOURS : une suite d'étapes numérotées, chacune un geste ou ce qui se passe ensuite. */
.aide-parcours {
  margin: 0 0 14px;
}

.aide-parcours h3 {
  margin: 14px 0 4px;
  font-size: 15px;
  color: var(--txt);
}

.aide-parcours h3:first-child {
  margin-top: 0;
}

.aide-parcours ol {
  margin: 0;
  padding-left: 22px;
  color: var(--txt2);
}

.aide-parcours ol li + li {
  margin-top: 4px;
}

/* Sur un écran étroit, le mot passe au-dessus de sa définition plutôt que de l'écraser. */
@media (max-width: 600px) {
  .aide-mots {
    grid-template-columns: 1fr;
  }

  .aide-mots dd {
    margin-bottom: 6px;
  }
}

/* ─────────── les indicateurs (#857) ─────────── */

.kpi-tete {
  margin-bottom: 12px;
}

.kpi-periodes {
  display: flex;
  gap: 6px;
  margin-bottom: 6px;
}

.kpi-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 12px;
  align-items: start;
}

.kpi-bloc h4 {
  margin: 12px 0 6px;
  font-size: 13px;
  color: var(--txt2);
}

.kpi-question {
  margin: -6px 0 10px;
  color: var(--txt2);
}

/* Le chiffre qui se TAIT se voit autant que celui qui parle : c'est une réponse, pas un trou. */
.kpi-aveu {
  color: var(--muted);
  font-style: italic;
}

.kpi-n,
.kpi-couverture {
  color: var(--muted);
  font-size: 12px;
}

.kpi-couverture {
  margin-top: 10px;
  border-top: 1px solid var(--line);
  padding-top: 6px;
}

.kpi-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.kpi-table th,
.kpi-table td {
  text-align: left;
  padding: 3px 6px;
  border-bottom: 1px solid var(--line);
}

.kpi-table th {
  color: var(--muted);
  font-weight: 500;
}

/* Les courbes du flux (#1322) : un SVG dans un repère fixe, étiré à la largeur du bloc. Chaque série
   a UNE classe, qui teint son trait, son aire, sa puce de légende et celle du viseur. */
#vue-indicateurs > .kpi-bloc {
  margin-bottom: 12px;
}

.kpi-courbe {
  position: relative;
}

.kpi-courbe svg {
  overflow: visible;
  display: block;
  width: 100%;
  height: auto;
}

.kpi-grille-ligne {
  stroke: var(--line);
  stroke-width: 1;
}

.kpi-axe {
  fill: var(--muted);
  font-size: 11px;
}

.kpi-trait {
  fill: none;
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.kpi-aire {
  stroke: none;
  opacity: .18;
}

.kpi-viseur-ligne {
  stroke: var(--muted);
  stroke-dasharray: 4 3;
}

.kpi-viseur {
  position: absolute;
  top: 0;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
  box-shadow: var(--shadow);
  font-size: 12px;
  pointer-events: none;
  white-space: nowrap;
}

.kpi-legende {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin-top: 4px;
  font-size: 13px;
  color: var(--txt2);
}

.kpi-puce {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-right: 6px;
  border-radius: 2px;
  vertical-align: -1px;
}

.kpi-chiffres {
  margin-top: 8px;
}

.kpi-chiffres summary {
  cursor: pointer;
  color: var(--txt2);
  font-size: 13px;
}

.kpi-s-entree,
.kpi-s-encours {
  stroke: var(--accent);
  fill: var(--accent);
  background: var(--accent);
}

.kpi-s-sortie,
.kpi-s-livre {
  stroke: var(--info);
  fill: var(--info);
  background: var(--info);
}

.kpi-trait.kpi-s-entree,
.kpi-trait.kpi-s-encours,
.kpi-trait.kpi-s-sortie,
.kpi-trait.kpi-s-livre {
  fill: none;
}

.kpi-liste {
  margin: 0;
  padding-left: 18px;
}

/* Un jeu de chiffres par carnet (#1964) : chacun s'annonce, et le second se détache du premier. */
.kpi-jeu + .kpi-jeu {
  margin-top: 28px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

.kpi-carnet {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 15px;
}

.kpi-pied {
  margin-top: 4px;
}

@media (max-width: 600px) {
  .kpi-grille {
    grid-template-columns: 1fr;
  }
}

/* Les vignettes de tête (#991) : cinq chiffres d'un coup d'œil, comme en tête des KPI d'Artis. */
.kpi-vignettes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}

.kpi-vignette {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
}

.kpi-valeur {
  font-size: 22px;
  font-weight: 600;
  margin: 2px 0;
}

/* L'export est un lien vers la route : il se lit comme un bouton, rangé au bout des périodes. */
/* L'HISTORIQUE (#1294) : les projets, la barre de recherche et la période en tête, puis deux
   panneaux comme Terminus — la colonne « Fait » faite des cartes du tableau, et la conversation de la
   carte choisie, dont les lignes sont celles de la fiche (`ligneDeFrise`), sa zone d'écriture aussi. */
.hist-barre .kpi-periodes {
  margin-bottom: 0;
}

.hist-bandeau {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.hist-corps {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

/* Le « CR » d'une carte mène ici : dans l'historique, la carte elle-même choisit son fil. */
.hist-cartes .t-cr {
  display: none;
}

.hist-cartes .tcarte.hist-choisie {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

/* La conversation tient l'écran et défile pour elle-même, la zone d'écriture restant en pied —
   la même hauteur que la colonne voisine. */
.hist-conversation {
  display: flex;
  flex-direction: column;
  max-height: calc(var(--haut-ecran) - var(--barre-fenetre, 0px) - 250px);
  min-height: 220px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.hist-tete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-bottom: 8px;
}

.hist-titre {
  font-weight: 600;
  color: inherit;
  text-decoration: none;
}

.hist-quand {
  margin-left: auto;
}

.hist-fil {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.hist-ecrire {
  flex: none;
  margin-top: 8px;
}

/* Sur un écran étroit, la colonne passe au-dessus de la conversation. */
@media (max-width: 900px) {
  .hist-corps {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Au téléphone, le compte rendu est le détail plein écran du gabarit liste → détail (#1752) : son
   titre y est déjà, en tête à côté de « ← ». */
@media (max-width: 600px) {
  .ld-detail .hist-titre {
    display: none;
  }
}

/* LES CONVERSATIONS (#1321) : les panneaux de l'Historique, une carte par fil à gauche. */
.conv-ticket {
  width: 110px;
}

.conv-carte {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: 100%;
  margin-bottom: 6px;
  padding: 8px 10px;
  text-align: left;
  color: inherit;
  font: inherit;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  cursor: pointer;
}

.conv-carte.conv-choisie {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.conv-titre {
  font-weight: 600;
}

.conv-pied {
  font-size: 12px;
}

.kpi-periodes a.btn-sm {
  margin-left: auto;
  text-decoration: none;
}

/* ─────────── liste → détail, au téléphone (#1749) ───────────
 *
 * Le gabarit que partagent Terminus, l'Historique et les Conversations, façon Messenger : la racine
 * `.ld` tient l'en-tête `.ld-tete`, la liste des cartes, et le détail `.ld-detail` — qui ne se montre
 * qu'OUVERT (`.ld-ouvert`, posé par `js/liste-detail.js`), en calque sur toute la hauteur visible,
 * avec « ← » en tête (`.ld-barre`). Le calque laisse la liste dessous, à sa position de défilement.
 * Au-dessus de 600 px, rien : `.ld-barre` y reste masquée et les panneaux gardent leur place. */
.ld-barre {
  display: none;
}

@media (max-width: 600px) {
  /* Épinglé sous la barre et la rangée d'onglets (la même hauteur que `scroll-padding-top` du bloc
     téléphone) ; une page qui défile autrement, comme Terminus, pose `--ld-haut`. Il déborde sur la
     marge de `main` pour que les cartes ne passent pas visibles sur ses côtés. */
  .ld-tete {
    position: sticky;
    top: var(--ld-haut, calc(var(--haut-barre) + 52px));
    z-index: 20;
    margin: 0 -12px;
    padding: 8px 12px;
    background: var(--bg);
  }

  /* La liste défile avec la page, et non dans une boîte à elle (`.hist-corps` est déjà sur une
     colonne sous 900 px). */
  .ld .colonne-corps {
    max-height: none;
    overflow: visible;
  }

  .ld-detail {
    display: none;
  }

  /* Au-dessus de la barre (30) et des onglets (25), sous les panneaux de notification. Affiché aussi
     `.ld-sortant`, le temps qu'il ressorte (#1763). */
  .ld:is(.ld-ouvert, .ld-sortant) .ld-detail {
    position: fixed;
    inset: 0 0 auto;
    z-index: 40;
    display: flex;
    flex-direction: column;
    height: calc(var(--haut-ecran) - var(--barre-fenetre, 0px));
    padding: 8px 12px 12px;
    background: var(--bg);
    overscroll-behavior: contain;
  }

  /* LE GLISSEMENT (#1763) : le détail entre par la droite et y ressort, la liste reste où elle est.
     La sortie ne tient que par `.ld-sortant`, que `js/liste-detail.js` lève en fin d'animation ; une
     animation à `none` — mouvement réduit, ou geste retour qu'iOS a déjà animé (`.ld-immediat`) — le
     lui dit, et il referme sans attendre. */
  .ld.ld-ouvert .ld-detail {
    animation: ld-entre 0.22s ease-out;
  }

  .ld.ld-sortant .ld-detail {
    animation: ld-sort 0.2s ease-in forwards;
    pointer-events: none;
  }

  .ld.ld-immediat .ld-detail {
    animation: none;
  }

  .ld-barre {
    display: flex;
    flex: none;
    align-items: center;
    gap: 10px;
    margin-bottom: 8px;
  }

  .ld-retour {
    flex: none;
    min-width: 44px;
    font-size: 18px;
  }

  .ld-titre {
    min-width: 0;
    overflow: hidden;
    font-weight: 600;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  /* Le fil prend tout ce que le calque laisse, au lieu de sa hauteur de panneau. */
  .ld-detail .hist-conversation {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
  }
}

@keyframes ld-entre {
  from { transform: translateX(100%); }
}

@keyframes ld-sort {
  to { transform: translateX(100%); }
}

@media (max-width: 600px) and (prefers-reduced-motion: reduce) {
  .ld:is(.ld-ouvert, .ld-sortant) .ld-detail {
    animation: none;
  }
}

/* ─────────── au téléphone (#1188) ───────────
 *
 * L'app COMPLÈTE, à 360–430 px : c'est l'arbitrage de #1175, et l'écran à part de /pilotage ne
 * revient pas (#1073). Rien ici ne touche au-dessus de 600 px.
 *
 * Ce bloc est le DERNIER de la feuille, et ce n'est pas du rangement : une @media ne pèse rien dans
 * la spécificité, donc à égalité c'est la position qui départage (#826). Placé plus haut, il serait
 * écrasé par les règles qu'il existe pour défaire.
 *
 * - LA BARRE DU HAUT se resserre sur UNE rangée de 56 px, sans sous-titre, la marque réduite. Elle
 *   reste COLLANTE comme les onglets (#1748 ; #1188 l'avait rendue au défilement) : `--haut-barre`
 *   dit sa hauteur, et tout ce qui s'ancre dessous — onglets, aperçu — suit la même valeur.
 * - LE CLAVIER : `--haut-ecran` y devient la hauteur VISIBLE, celle que le clavier laisse (#1748), et
 *   un champ qui prend le focus défile jusque sous la barre et les onglets, pas dessous.
 * - LE BOUTON 📁 n'y est pas (#1748) : un téléphone n'a pas d'éditeur où ouvrir un dossier. Tous
 *   ceux de `boutonDeDossierHtml` portent `data-ouvrir` ; l'aide cache ce qui en parle
 *   (`.hors-telephone`). En CSS et non dans la fabrique : tourner l'écran ne redessine rien.
 * - LES ONGLETS tiennent sur UNE rangée qui défile : sur trois rangées collantes, ils prenaient le
 *   quart de l'écran à chaque défilement.
 * - LE TABLEAU montre une colonne à la fois, en défilement de côté aimanté ; 86 % et non 100 %,
 *   pour que le bord de la suivante dise qu'il y en a une. Sa position survit aux dessins
 *   (`CLE_DEFILEMENT_TABLEAU`). Changer d'état sans glisser : « ↔ État… » sur la fiche. */
@media (max-width: 600px) {
  :root {
    --haut-barre: 56px;
    --haut-ecran: var(--haut-visible, 100dvh);
    /* la barre, puis la rangée d'onglets (40 px de bouton et sa marge) */
    scroll-padding-top: calc(var(--haut-barre) + 52px);
  }

  .topbar {
    flex-wrap: nowrap;
    gap: 8px;
    padding: 0 12px;
  }

  [data-ouvrir],
  .hors-telephone {
    display: none;
  }

  .marque {
    width: 40px;
    height: 40px;
  }

  .brand {
    min-width: 0;
    gap: 8px;
  }

  .brand h1 {
    font-size: 18px;
  }

  .brand .sub {
    display: none;
  }

  .topbar-right {
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
  }

  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding: 6px 12px 0;
    scrollbar-width: none;
  }

  .tabs button {
    flex: none;
    padding: 9px 12px;
  }

  /* LA RANGÉE DIT QU'ELLE CONTINUE (#1603) : sa barre de défilement est masquée, et un onglet coupé
     net au bord ne se lisait pas comme « glissez ». Un fondu sur chaque bord où il reste des onglets,
     posé par `signalerLesOngletsCaches` — éteint au bout de la rangée, pour ne pas voiler « ? Aide ».
     COLLANTS dans la rangée : ils suivent le bord visible et non le contenu. La marge négative
     rend leur place et l'écart de la rangée, pour qu'ils ne décalent aucun onglet. */
  .tabs::before,
  .tabs::after,
  :is(#vue-tickets, .ld-tete) #barre-projets::before,
  :is(#vue-tickets, .ld-tete) #barre-projets::after {
    content: "";
    position: sticky;
    z-index: 1;
    flex: none;
    width: 28px;
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s;
  }

  .tabs::before {
    left: 0;
    margin-right: -32px;
    background: linear-gradient(to left, transparent, var(--barre-bas));
  }

  .tabs::after {
    right: 0;
    margin-left: -32px;
    background: linear-gradient(to right, transparent, var(--barre-bas));
  }

  .tabs.plus-a-gauche::before,
  .tabs.plus-a-droite::after,
  :is(#vue-tickets, .ld-tete) #barre-projets.plus-a-gauche::before,
  :is(#vue-tickets, .ld-tete) #barre-projets.plus-a-droite::after {
    opacity: 1;
  }

  /* ─── l'en-tête de la vue Tickets (#1750) ───
     De haut en bas : les projets sur UNE ligne qui défile, comme les onglets — leurs fondus, sur le
     fond de la page, et `signalerLesBordsCaches` qui les allume ; puis la recherche, l'entonnoir et
     le tri, sur une ligne de 40 px ; puis « Nouveau ticket… ». Replié, rien d'autre.
     La ligne des projets vaut aussi dans l'en-tête épinglé d'une liste → détail (#1753). */
  :is(#vue-tickets, .ld-tete) #barre-projets {
    flex-wrap: nowrap;
    overflow-x: auto;
    margin-bottom: 10px;
    scrollbar-width: none;
  }

  :is(#vue-tickets, .ld-tete) #barre-projets > * {
    flex: none;
  }

  :is(#vue-tickets, .ld-tete) #barre-projets::before {
    margin-right: -34px;
    background: linear-gradient(to left, transparent, var(--bg));
  }

  :is(#vue-tickets, .ld-tete) #barre-projets::after {
    margin-left: -34px;
    background: linear-gradient(to right, transparent, var(--bg));
  }

  /* Une grille de trois pistes dont la première ligne est posée : la recherche, l'entonnoir, et la
     place du tri — lui est dans le pli, d'où il sort en absolu pour rester là, plié ou non.
     Tout le reste prend une ligne entière, dans l'ordre du document ; le pli vient après
     « Nouveau ticket… » et « Bilan… » (`order`), juste au-dessus du panneau Avancé qu'il ouvre. */
  .barre-outils {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 40px 40px;
    gap: 8px;
    margin-bottom: 12px;
  }

  .barre-outils > * {
    grid-column: 1 / -1;
  }

  .barre-outils > #rech {
    grid-row: 1;
    grid-column: 1;
    min-width: 0;
    height: 40px;
  }

  .barre-outils > #bascule-filtres {
    grid-row: 1;
    grid-column: 2;
  }

  .barre-outils > .pli-filtres {
    order: 1;
  }

  .barre-outils > .btn-sm {
    min-height: 40px;
  }

  /* L'entonnoir et le tri : deux carrés de 40 px, l'icône au centre. Le nombre de filtres posés
     est une pastille sur le coin de l'entonnoir ; ouvert, il prend l'accent comme « Avancé ». */
  .bascule-filtres,
  .tri-hote {
    position: relative;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--panel2);
    color: var(--txt2);
  }

  .bascule-filtres[aria-expanded="true"] {
    border-color: var(--accent);
    background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--panel2);
    color: var(--accent);
  }

  .bascule-filtres .icone,
  .tri-hote .icone {
    width: 20px;
    height: 20px;
  }

  .bascule-filtres .compte {
    position: absolute;
    top: -6px;
    right: -6px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--accent);
    color: var(--bg);
    font-size: 11px;
    font-weight: 700;
    line-height: 18px;
  }

  /* Le tri reste le <select> de la barre, rendu invisible PAR-DESSUS l'icône : le toucher ouvre le
     sélecteur natif du téléphone, et l'ordre n'a toujours qu'une écriture. */
  .tri-hote {
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
  }

  .tri-icone {
    display: block;
    pointer-events: none;
  }

  .tri-hote .tri {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
  }

  /* ─── le panneau des filtres, déplié (#1750) ───
     UNE carte, et non plus une suite de contrôles de tailles diverses posés au fil de la barre.
     Trois groupes, dans l'ordre d'avant : les listes, en pleine largeur ; les cases, une ligne de
     44 px chacune sous un filet ; les pastilles « Non attribué » / « Mes tickets », côte à côte.
     « Avancé » ferme la carte, au-dessus du panneau qu'il ouvre. */
  .pli-filtres {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel2);
  }

  .pli-filtres > * {
    grid-column: 1 / -1;
  }

  .pli-filtres .liste-filtre {
    width: 100%;
    min-height: 44px;
  }

  .pli-filtres .interrupteur {
    min-height: 44px;
    margin: 0 -14px;
    padding: 0 14px;
    border-top: 1px solid var(--line);
    color: var(--txt2);
    font-size: 14px;
  }

  /* Le premier filet sépare le groupe des listes de celui des cases : un peu d'air au-dessus. */
  .pli-filtres > #bascule-avance + .interrupteur {
    margin-top: 6px;
  }

  .pli-filtres .chip {
    grid-column: auto;
    justify-content: center;
    min-height: 40px;
    font-size: 13px;
  }

  .pli-filtres .hint {
    margin: 0;
    font-size: 12.5px;
  }

  .pli-filtres > #bascule-avance {
    order: 1;
    min-height: 44px;
    font-size: 14px;
  }

  /* ─── la barre des Conversations (#1753) ───
     La recherche et l'entonnoir sur une ligne de 40 px ; ouvert, le pli est la carte ci-dessus :
     le ticket et le nombre de participants côte à côte, puis une case par ligne. Replié, rien. */
  .conv-barre {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 40px;
    gap: 8px;
    margin-bottom: 0;
  }

  .conv-barre > .recherche {
    min-width: 0;
    height: 40px;
  }

  .conv-barre > .pli-filtres {
    grid-column: 1 / -1;
  }

  .conv-barre:has(.bascule-filtres[aria-expanded="false"]) > .pli-filtres {
    display: none;
  }

  .conv-barre .pli-filtres > :is(input, select) {
    grid-column: auto;
    width: 100%;
    min-height: 44px;
  }

  .conv-barre .pli-filtres > select + .interrupteur {
    margin-top: 6px;
  }

  /* Le panneau Avancé prolonge la carte : chaque facette en bloc, son nom AU-DESSUS de ses
     pastilles — à côté, le nom mangeait un tiers de la ligne et les pastilles s'y tassaient. */
  #vue-tickets .avance {
    gap: 10px;
    margin-bottom: 12px;
  }

  #vue-tickets .facette {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    padding: 12px 14px;
  }

  #vue-tickets .facette-nom {
    min-width: 0;
    font-size: 13px;
    font-weight: 600;
  }

  #vue-tickets .facette-choix {
    gap: 8px;
  }

  #vue-tickets .facette-choix .chip {
    min-height: 36px;
    font-size: 13px;
  }

  #vue-tickets .facette-pied {
    justify-content: space-between;
    padding: 0 2px;
  }

  #vue-tickets .facette-pied .btn-sm {
    min-height: 40px;
  }

  /* Les boutons d'installation de l'app de bureau (#1603) : elle ne se pose que sur un poste Windows,
     et « Télécharger l'app » / « Déjà installée ? » prenaient une rangée entière de la barre. « Lancer
     l'app » part avec eux : le schéma rprime:// n'existe pas sur un téléphone. La cloche reste. */
  .ouvrir-rprime {
    display: none;
  }

  /* « Remonter » quitte la rangée qui défile pour la barre du haut, à côté de la cloche (#1603) : au
     bout de la rangée, personne ne le trouvait. Dans la fenêtre de bureau, qui n'a pas de barre du
     haut, il reste dans les onglets. */
  html:not(.fenetre-bureau) .tabs #remonter {
    display: none;
  }

  .remonter-haut:not([hidden]) {
    display: inline-flex;
  }

  main {
    padding: 12px;
  }

  .kanban {
    grid-template-columns: none;
    grid-auto-columns: 86%;
    grid-auto-flow: column;
    gap: 10px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
  }

  .colonne {
    scroll-snap-align: start;
  }

  /* ─── le plan déployé, première colonne du tableau (#1750) ───
     Il passait AU-DESSUS des colonnes (une piste sous 1100 px) : ouvert, il repoussait le tableau
     sous un écran entier. Déployé, c'est `.avec-plan` qui défile de côté, aimanté, et non plus
     `.kanban` : la colonne du plan, puis celles du tableau, dans le même défilement. Toutes font
     86 % de la largeur utile (`--piste-telephone`), lue sur la FENÊTRE moins les marges de `main` :
     une colonne d'état ne sait pas la largeur de `.avec-plan`, deux boîtes plus haut. Pas d'unités
     de conteneur : `container-type` ferait de `.avec-plan` le repère des `position: fixed` qu'il
     porte — la zone de dépôt de clôture se poserait dans le tableau au lieu du bas de l'écran.
     Les marque-pages restent en tête, collés au bord gauche quand on défile. Replié, rien de tout
     ça : le tableau défile seul, comme avant. */
  .avec-plan.deploye {
    --piste-telephone: calc((100vw - 24px) * .86);
    grid-template-columns: var(--piste-telephone) max-content;
    gap: 10px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
  }

  .avec-plan.deploye > #plan-marques {
    position: sticky;
    left: 0;
    grid-column: 1 / -1;
    width: calc(100vw - 24px);
  }

  .avec-plan.deploye > #plan-rail {
    grid-column: 1;
    min-width: 0;
    scroll-snap-align: start;
  }

  .avec-plan.deploye > #tableau {
    grid-column: 2;
  }

  .avec-plan.deploye .kanban {
    grid-auto-columns: var(--piste-telephone);
    overflow-x: visible;
    scroll-snap-type: none;
  }

  .fiche-titre {
    font-size: 18px;
  }

  /* L'onglet Terminus EMPILE le cadre et la colonne du plan d'un pilote (#1249) : côte à côte, les
     320 px de la colonne ne laissaient rien au cadre. Le plan passe dessous, et ses étapes y défilent. */
  .onglet-terminus-corps {
    flex-direction: column;
    align-items: stretch;
  }

  /* En colonne, `flex: 1` prendrait la hauteur du CONTENU d'une iframe (150 px, planchée à 420) au lieu
     de la sienne. */
  .onglet-terminus-corps .onglet-terminus-cadre {
    flex: none;
  }

  /* Sans plancher : sous le clavier, le cadre se cale sur ce qui reste, et la saisie de Terminus
     avec lui (#1748). */
  .onglet-terminus-cadre {
    min-height: 0;
  }

  .onglet-terminus-plan {
    width: auto;
    height: auto;
    min-height: 0;
    max-height: 60vh;
  }
}

/* LE DOIGT, et non la largeur : un téléphone en paysage ou une tablette dépassent 600 px et ont
 * toujours un doigt pour pointeur. Aucune de ces cibles ne se découvre au survol (vérifié : les
 * `:hover` de cette feuille ne font que teinter) ; il leur manquait la TAILLE — ▶ ▶▶ 📁 faisaient
 * 24 × 20 px, un « Répondre » 11 px de texte nu. 40 px de haut au moins, près des 44 que
 * recommandent les guides des téléphones sans doubler la hauteur d'une carte.
 * Les champs passent à 16 px : en dessous, Safari agrandit la page au focus et ne la rend pas. */
@media (pointer: coarse) {
  .btn-sm,
  .chip,
  .tri,
  .liste-filtre,
  .fiche-etat,
  .tabs button,
  .tab-lien,
  .fil-plus {
    min-height: 40px;
  }

  .t-lance,
  .t-dossier {
    width: 40px;
    height: 36px;
    font-size: 14px;
  }

  .fil-plus {
    font-size: 13px;
  }

  /* Le libellé EST la cible de sa case : c'est lui qui grandit (#1605). */
  .interrupteur {
    min-height: 40px;
  }

  .interrupteur input[type="checkbox"] {
    width: 18px;
    height: 18px;
  }

  .pl-replier,
  .tcarte .t-prendre {
    min-width: 40px;
    min-height: 40px;
  }

  /* Des glyphes sans cadre, sur des lignes qui ne doivent pas grandir : la zone de 40 px vient du
     padding, et les marges verticales négatives la rendent à la mise en page — la carte, l'en-tête
     de colonne et l'étape gardent leur hauteur. Aucun de leurs conteneurs ne coupe ce qui déborde
     (`overflow`), qui couperait aussi le toucher. Les marges de côté restent celles de la base. */
  .pl-x,
  .colonne h4 .col-choix,
  .tcarte .t-choix {
    min-width: 40px;
    min-height: 40px;
    margin-top: -12px;
    margin-bottom: -12px;
  }

  /* Les consignes de la souris cèdent la place à celle du doigt (#1602, cf. `.hint.au-doigt`). */
  .hint.au-souris {
    display: none;
  }

  .hint.au-doigt {
    display: inline;
  }

  input,
  select,
  textarea,
  .recherche,
  .dlg input,
  .dlg textarea,
  .fil-ecrire textarea {
    font-size: 16px;
  }
}

/* Une tablette au doigt dépasse 600 px et n'installe pas davantage l'app de bureau (#1603) : ses
   boutons partent aussi. `hover: none` garde un portable Windows à écran tactile, dont le pointeur
   principal reste la souris — lui peut poser l'app. */
@media (pointer: coarse) and (hover: none) {
  .ouvrir-rprime {
    display: none;
  }
}

/* ─────────── les équipes d'un projet, dans « Partager… » (#1405) ─────────── */

.partage-equipes {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

.partage-equipes h3 {
  margin: 0;
  font-size: 13px;
}

.partage-equipes > p {
  margin: 0;
  font-size: 11.5px;
}

/* Qui n'entre que par une équipe n'a ni rôle ni accès à régler : la phrase qui le dit prend la
   place des trois contrôles, sur le reste de la ligne. */
.partage-par-equipe {
  grid-column: 3 / -1;
  font-size: 11.5px;
}

.partage-a-savoir {
  margin: 4px 2px 0;
  font-size: 11.5px;
}

.groupe-projets {
  margin: 4px 0 6px;
  font-size: 11.5px;
}
