/* MaintAI — style minimal du socle (phase 1) */
* { box-sizing: border-box; }
/* ============================================================
 * LA PALETTE — un rôle, un token (2026-09-07)
 * ------------------------------------------------------------
 * Valeurs relevées sur le thème g10 d'IBM Carbon, le design system de
 * Maximo : c'est le registre que connaissent les clients sous GMAO, et ses
 * contrastes sont documentés. Avant ce jour : 468 couleurs en dur dans ce
 * fichier, 87 teintes distinctes, AUCUN token — dont DEUX bleus primaires
 * concurrents (#4f46e5 pour les boutons, #2563eb pour l'étape courante).
 *
 * 🔴 ON NOMME PAR RÔLE, JAMAIS PAR TEINTE. `--bord` et non `--gris-clair` :
 * le jour où un client veut son thème, on change la valeur sans relire une
 * seule règle. C'est ce qui rend un thème client possible en vingt lignes.
 *
 * ⚠️ `--alerte-texte` et `--succes-texte` ne viennent PAS de Carbon : il n'y
 * expose aucune couleur de texte ambre ou verte lisible (son #f1c21b sur
 * blanc ne donne que 1,7:1). Elles sont CALCULÉES en assombrissant la couleur
 * Carbon jusqu'à 4,5:1 sur leur propre fond — pas choisies à l'œil.
 * ============================================================ */
:root {
  --surface: #ffffff;
  --fond: #f4f4f4;
  --bord: #e0e0e0;
  --bord-2: #c6c6c6;
  --bord-fort: #8d8d8d;
  --texte: #161616;
  --texte-2: #525252;
  --texte-3: #6f6f6f;
  --sur-couleur: #ffffff;
  --action: #0f62fe;
  --action-survol: #0050e6;
  --action-2: #393939;
  --surlignage: #d0e2ff;
  --erreur: #da1e28;
  --erreur-fond: #fff1f1;
  --alerte: #f1c21b;
  --alerte-fond: #fcf4d6;
  --alerte-texte: #876d0f;
  --succes: #24a148;
  --succes-fond: #defbe6;
  --succes-texte: #1c7e38;
  --info: #0043ce;
  --info-fond: #edf5ff;
  /* « À vérifier » (2026-09-11) : l'orange Carbon, DISTINCT de l'ambre du
     seuil Renforcé — une ligne Renforcé à vérifier doit se voir deux fois.
     Texte : orange-70 Carbon, 7,4:1 sur son fond. */
  --verifier: #ff832b;
  --verifier-fond: #fff2e8;
  --verifier-texte: #8a3800;

  /* --- Typographie : les cinq tailles qui portent 83 % des usages
     ne bougent pas d’un pixel ; seules les valeurs isolées s’y rabattent,
     dont le 12.5px qui voisinait un 12px et un 13px. --- */
  --txt-xs: 10px;
  --txt-s: 11px;
  --txt-m: 12px;
  --txt-l: 13px;
  --txt-xl: 14px;
  --txt-2xl: 15px;
  --txt-titre: 18px;
  --txt-chiffre: 30px;
  --txt-geant: 44px;

  /* --- Rayons --- */
  --r-xs: 4px;
  --r-s: 6px;
  --r-m: 8px;
  --r-l: 10px;
  --r-xl: 12px;
  --r-pilule: 999px;
  --r-cercle: 50%;

  /* --- Élévations. Les `inset` du fichier ne sont PAS des élévations :
     ce sont des liserés sémantiques, ils gardent leur couleur de rôle. --- */
  --ombre-1: 0 1px 4px rgba(0,0,0,.06);
  --ombre-2: 0 2px 8px rgba(0,0,0,.07);
  --ombre-3: 0 10px 34px rgba(0,0,0,.10);
  --ombre-action: 0 3px 12px rgba(15, 98, 254, .20);
  --anneau: 0 0 0 2px var(--surlignage);

  /* --- Les quatre fonds TRANSLUCIDES. Un token hexadécimal ne porte pas
     l'alpha : ils s'écrivaient donc en `rgba()` et ont TRAVERSÉ le lot des
     couleurs sans être vus — trois d'entre eux portaient encore l'ancienne
     palette (l'indigo 79,70,229 et le fond 248,250,252) alors que tout le
     reste du fichier avait changé. C'est le trou que `check_couleurs.js`
     ferme désormais : plus aucun `rgba()` hors de ce bloc. --- */
  --cellule-modifiee: rgba(241, 194, 27, .28);
  --survol-action: rgba(15, 98, 254, .05);
  --voile: rgba(244, 244, 244, .94);
  --voile-discret: rgba(244, 244, 244, .62);
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
  background: var(--fond);
  color: var(--texte);
}
.logo-row { display: flex; align-items: center; gap: 10px; }
/* 🎨 LE LOGO MaintAI© (2026-09-11) — tiré des prototypes de Pascal : l'engrenage
   orange au nuage « AI », le mot « Maint » et « AI » en orange. Il remplace la
   pastille « M » en dégradé. Ce sont des IMAGES, et c'est voulu : un logo ne se
   recompose pas avec une police système, et ses couleurs appartiennent à la
   marque, pas à la palette de l'interface — qui garde son bleu d'action.
   ⚠️ Fond TRANSPARENT (le blanc des prototypes a été retiré) : le bandeau reste
   juste sur le blanc de la barre du haut comme sur le gris de la page.
   Sources : public/img/maintai-bandeau.png (engrenage + mot, sans la devise),
   maintai-logo.png (complet, pour la connexion), maintai-icone.png (onglet). */
.logo-produit { display: block; height: 40px; width: auto; }
.logo-connexion { margin: 0 0 6px; }
.logo-connexion img { display: block; width: 100%; max-width: 320px; height: auto; }

/* Login */
.login-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.login-card {
  background: var(--surface); border-radius: var(--r-xl); padding: 36px;
  box-shadow: var(--ombre-3); width: 380px;
}
.login-card h1 { margin: 0; font-size: var(--txt-chiffre); }
.subtitle { color: var(--texte-2); font-size: var(--txt-l); margin: 6px 0 22px; }
label { display: block; font-size: var(--txt-l); font-weight: 600; margin: 14px 0 4px; }
input {
  width: 100%; padding: 10px 12px; border: 1px solid var(--bord-2);
  border-radius: var(--r-m); font-size: var(--txt-xl);
}
input:focus { outline: 2px solid var(--action); border-color: transparent; }
button {
  margin-top: 18px; width: 100%; padding: 11px; border: none; border-radius: var(--r-m);
  background: var(--action); color: var(--surface); font-size: var(--txt-2xl); font-weight: 600; cursor: pointer;
}
button:hover { background: var(--action); }
button:disabled { opacity: .6; cursor: wait; }
.error { color: var(--erreur); font-size: var(--txt-l); margin-top: 10px; }

/* Choix de la langue, sur les écrans d'AVANT la connexion.
   Discret, mais toujours visible : quelqu'un qui ne lit pas le français doit
   pouvoir en changer sans avoir à comprendre le français pour le demander.
   Le `label` repasse en ligne — la règle générale des formulaires le met en
   bloc, ce qui rejetterait le menu sous son étiquette pour deux mots. */
.ligne-langue {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 8px; margin-bottom: 18px;
}
.ligne-langue label {
  display: inline; margin: 0; font-weight: 400;
  font-size: var(--txt-m); color: var(--texte-3);
}
.selecteur-langue {
  padding: 6px 8px; border: 1px solid var(--bord-2); border-radius: var(--r-s);
  font-size: var(--txt-l); background: var(--surface); color: var(--texte);
  cursor: pointer;
}
.selecteur-langue:focus { outline: 2px solid var(--action); border-color: transparent; }

/* Accueil */
.topbar {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--surface); padding: 12px 24px; box-shadow: var(--ombre-1);
}
.user-zone { display: flex; align-items: center; gap: 12px; font-size: var(--txt-xl); }
/* Sélecteur de site — c'est le contexte de travail, il doit se voir */
.user-zone select {
  padding: 8px 10px; border: 1px solid var(--bord-2); border-radius: var(--r-m);
  font-size: var(--txt-xl); font-weight: 600; background: var(--surface); color: var(--texte); max-width: 260px;
}
.user-zone select:disabled { background: var(--sur-couleur); color: var(--texte-2); cursor: default; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* 🔴 `.btn-primary` ÉTAIT EMPLOYÉE QUATRE FOIS SANS EXISTER (corrigé le
   2026-09-07). Les quatre boutons — « ✓ Garder les deux », « ↻ Reprendre les
   lots restants » ×2, « Appliquer les décisions » — ne devaient leur apparence
   qu'à la règle générique `button` ci-dessus. Ça marchait par accident : le
   jour où ce générique change, quatre boutons d'action suivent sans que rien
   ne le dise.
   ⚠️ Le diagnostic du 2026-09-03 annonçait qu'ils sortaient EN PLEINE LARGEUR.
   Mesuré dans leur vrai gabarit, c'est faux : leurs quatre usages sont dans une
   `.barre-actions`, qui impose déjà `width: auto`. Le « 600 px » venait d'un
   bouton isolé, cas qui n'existe nulle part dans le produit. */
.btn-primary {
  width: auto; margin: 0; padding: 8px 14px;
  background: var(--action); color: var(--sur-couleur); font-size: var(--txt-l);
}

.btn-secondary {
  width: auto; margin: 0; padding: 8px 14px;
  background: var(--bord-2); color: var(--texte); font-size: var(--txt-l);
}
.btn-secondary:hover { background: var(--bord-2); }
.container { max-width: 860px; margin: 30px auto; padding: 0 16px; }
.card {
  background: var(--surface); border-radius: var(--r-xl); padding: 22px 24px; margin-bottom: 22px;
  box-shadow: var(--ombre-2);
}
.card h2 { margin: 0 0 14px; font-size: var(--txt-titre); }
.create-form { display: flex; gap: 10px; }
/* 🔴 `flex: 1` SUR LE MENU DÉROULANT AUSSI, et c'est ce qui manquait. La règle
   générale donne `width: 100%` à tout `select` : dans une rangée en flex, cette
   largeur devient sa base de répartition, et il raflait la place en écrasant les
   deux champs de saisie à trente pixels. Poser `flex: 1` ramène sa base à zéro,
   et les trois éléments se partagent la ligne à parts égales.
   `min-width: 0` complète : sans lui, un élément de formulaire refuse de
   descendre sous la largeur de son contenu. Rien de tout cela ne se voit dans la
   source — seulement à l'écran. */
.create-form input { flex: 1; min-width: 0; }
.create-form select { flex: 1; min-width: 0; }
.create-form button { width: auto; margin: 0; padding: 10px 22px; }
.analyses-list { display: flex; flex-direction: column; gap: 8px; }
.analysis-row {
  padding: 12px 14px; border: 1px solid var(--bord-2); border-radius: var(--r-m);
  display: flex; justify-content: space-between; align-items: center;
}
.analysis-row:hover { border-color: var(--action); background: var(--fond); }
.muted { color: var(--texte-2); font-size: var(--txt-l); }
/* Clients et leurs sites (administrateur de plateforme) */
.client-bloc { padding: 12px 0; border-bottom: 1px solid var(--bord-2); }
.client-bloc:last-child { border-bottom: none; }
.client-bloc h3 { margin: 0 0 8px; font-size: var(--txt-2xl); }
.client-site { display: flex; align-items: center; gap: 10px; margin: 6px 0 6px 16px; }
/* Le nom du site prend une largeur FIXE : sans elle, les champs de saisie de
   deux lignes voisines ne s'alignent pas et la colonne paraît de travers. */
.client-site > span { flex: 0 0 160px; }
.client-site input { flex: 1; min-width: 0; margin: 0; }
.client-site button { width: auto; margin: 0; padding: 8px 16px; }
.client-bloc > button { width: auto; margin: 4px 0 0 16px; padding: 8px 16px; }
.client-superviseurs { margin: 10px 0 0 16px; padding-top: 8px; border-top: 1px dashed var(--bord-2); }
.client-superviseurs > strong { font-size: var(--txt-l); color: var(--texte-2); }
/* Les lignes de superviseur n'ont pas de colonne « nom de site » à aligner :
   leur premier élément reprend donc toute la largeur disponible. */
.client-superviseurs .client-site { margin-left: 0; }
.client-superviseurs .client-site > span { flex: 1; }
/* --- Écran « Mes sites » : les analyses en cartes ---------------------- */
.filtres-apercu { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 14px 18px; }
.filtres-apercu select { width: auto; min-width: 180px; margin: 0; }
.filtres-apercu label { font-size: var(--txt-xl); }
.case-groupe { display: flex; align-items: center; gap: 6px; }
.case-groupe input { width: auto; margin: 0; }

/* Une grille qui se remplit toute seule : à trois cartes par ligne sur un
   écran large, à une seule sur un portable, sans point de rupture à tenir. */
.grille-cartes {
  display: grid; gap: 16px; margin-bottom: 24px;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
.carte-analyse {
  background: var(--surface); border: 1px solid var(--bord-2); border-radius: var(--r-l);
  padding: 16px; display: flex; flex-direction: column; gap: 8px;
}
.carte-analyse:hover { border-color: var(--action); }
.carte-analyse h3 { margin: 0; font-size: var(--txt-titre); }
.carte-haut { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.badge-site {
  background: var(--alerte-fond); color: var(--alerte-texte); border-radius: var(--r-s);
  padding: 3px 8px; font-size: var(--txt-m); font-weight: 600;
}
.badge-secteur {
  align-self: flex-start; background: var(--surlignage); color: var(--action);
  border-radius: var(--r-s); padding: 3px 8px; font-size: var(--txt-m);
}

/* La barre des neuf étapes. Chaque colonne porte son trait et son nom : le nom
   sous le trait évite d'avoir à survoler pour savoir de quelle étape il s'agit
   — un diagnostic qui demande un survol ne se fait pas d'un coup d'œil. */
.barre-etapes { display: flex; gap: 4px; margin: 6px 0; }
.etape-col { flex: 1; min-width: 0; text-align: center; }
.etape-trait { height: 6px; border-radius: var(--r-xs); background: var(--bord-2); }
/* UNE SEULE LIGNE, tronquée proprement — le nom complet est au survol.
   Les deux essais précédents coupaient « Décompositio / n » : `overflow-wrap:
   anywhere` coupe n'importe où, et `hyphens: auto` n'a pas de dictionnaire
   français ici, donc il ne coupait pas mieux. Une césure au hasard se lit plus
   mal qu'un mot visiblement tronqué, et fait en plus varier la hauteur des
   colonnes : la barre entière paraissait de travers. */
.etape-nom {
  font-size: var(--txt-xs); color: var(--texte-2); margin-top: 3px; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Les trois états du bandeau, et les MÊMES couleurs : vert validé, ambre
   produit, gris rien. Un troisième jeu de couleurs pour la même information
   obligerait à réapprendre l'écran. */
.etape-valide { background: var(--succes); }
.etape-genere { background: var(--alerte); }
.etape-absent { background: var(--bord-2); }
/* Étape en stand-by : elle ne doit pas se confondre avec une étape simplement
   pas faite. Le premier essai les hachurait en deux gris trop proches — sur un
   trait de six pixels, les deux se ressemblaient. Un trait CREUX, en pointillé,
   se distingue d'un trait plein quelle que soit la taille.
   ⚠️ `box-sizing` : sans lui, la bordure s'ajoute à la hauteur et cette étape
   dépasse ses voisines d'un pixel — la barre paraît alors de travers. */
.etape-standby {
  background: transparent; border: 1px dashed var(--bord-2); box-sizing: border-box;
}

.carte-actions {
  display: flex; gap: 12px; align-items: center; justify-content: space-between;
  border-top: 1px solid var(--sur-couleur); padding-top: 10px; margin-top: 4px;
}
.carte-actions button { width: auto; margin: 0; }
/* « Reprendre » est l'action évidente d'une carte : un lien suffit. En gros
   bouton plein, il écrasait la carte entière et attirait l'œil avant la barre
   des étapes, qui est pourtant l'information. */
.btn-lien {
  background: none; border: none; box-shadow: none; padding: 5px 0;
  color: var(--action); font-weight: 600; text-decoration: none; font-size: var(--txt-xl);
}
.btn-lien:hover:not(:disabled) { text-decoration: underline; background: none; }

/* Journal d'audit : un accès ALM doit sauter aux yeux, c'est la question à
   laquelle ce journal répond. Le repérer en lisant les e-mails un par un
   supposerait de savoir par cœur qui travaille chez ALM. */
.audit-alm { font-weight: 600; color: var(--action); }
/* Logo du site */
.logo-row-edit { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.logo-preview {
  min-width: 180px; min-height: 72px; padding: 8px 12px;
  border: 1px dashed var(--bord-2); border-radius: var(--r-m);
  display: flex; align-items: center; gap: 10px; background: var(--surface);
}
.logo-preview img { max-width: 150px; max-height: 64px; }
/* Le cadre accepte le dépôt : il le montre au survol d'un fichier. */
.logo-preview.depot-survol { border-color: var(--action); background: var(--surlignage); }
.logo-row-edit input[type="file"] { width: auto; border: none; padding: 0; font-size: var(--txt-l); }
#logoDelete { margin-top: 10px; display: block; }
/* Une règle d'auteur qui fixe display neutralise le [hidden] du navigateur :
   sans cette ligne, le bouton resterait visible même sans logo. */
#logoDelete[hidden] { display: none; }

/* Bandeau d'avertissement — un changement de site subi doit se voir */
.notice {
  background: var(--alerte-fond); border: 1px solid var(--alerte); color: var(--alerte-texte);
  border-radius: var(--r-m); padding: 12px 14px; margin: 0 0 18px; font-size: var(--txt-xl);
}

/* ============================================================
   Étape 1 — acquisition des données
   ============================================================ */

/* Les zones de dépôt s'ajustent d'elles-mêmes : cinq catégories sur un grand
   écran, empilées sur un portable. Pas de point de rupture à maintenir. */
.dropzones {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 14px;
}
.dropzone {
  border: 1px dashed var(--bord-2); border-radius: var(--r-l);
  padding: 14px; background: var(--surface);
}
/* Une zone de dépôt SEULE — le cadre de l'étape 8, qui n'est pas dans une
   grille de catégories comme celles de l'étape 1. Le pointillé annonce qu'on
   peut y glisser quelque chose ; sans lui, rien à l'écran ne le laisserait
   deviner et le libellé passerait pour une décoration. */
.depot-zone {
  border: 1px dashed var(--bord-2); border-radius: var(--r-l);
  padding: 14px; background: var(--surface);
}
.depot-zone p { margin: 0 0 8px; }
.depot-zone.depot-survol { border-color: var(--action); background: var(--surlignage); }
.dropzone h3 { margin: 0 0 4px; font-size: var(--txt-2xl); }
.dropzone input[type="file"] { margin: 10px 0 6px; font-size: var(--txt-l); }

.file-list { list-style: none; margin: 6px 0 0; padding: 0; font-size: var(--txt-l); }
.file-list li {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 0; border-top: 1px solid var(--bord);
}
.file-list li:first-child { border-top: none; }

/* Action destructrice discrète : elle ne doit pas rivaliser visuellement avec
   les boutons d'action principaux de la page. */
.btn-link {
  margin-left: auto; background: none; border: none; padding: 2px 4px;
  color: var(--erreur); font-size: var(--txt-m); cursor: pointer; text-decoration: underline;
}
.btn-link:hover { color: var(--erreur); }

.params-grid {
  display: grid; grid-template-columns: 220px minmax(0, 320px);
  gap: 8px 16px; align-items: center;
}
.params-grid label { margin: 0; }
.params-grid input, .params-grid select { width: 100%; }
@media (max-width: 640px) {
  .params-grid { grid-template-columns: 1fr; }
}

select {
  width: 100%; padding: 10px 12px; border: 1px solid var(--bord-2);
  border-radius: var(--r-m); font-size: var(--txt-xl); background: var(--surface);
}

.row-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }

/* Le nom d'une analyse est un lien, mais il reste un titre : on ne le souligne
   qu'au survol pour ne pas alourdir la liste. */
.analysis-link { color: inherit; font-weight: 700; text-decoration: none; }
.analysis-link:hover { text-decoration: underline; color: var(--action); }

/* Tableau des activités du plan existant */
/* Le tableau défile DANS son cadre : une trentaine d'activités, sept colonnes,
   et sur un portable la page entière partirait en travers. */
.table-scroll { overflow-x: auto; margin-top: 12px; }

/* 🔴 DANS UN CADRE QUI DÉFILE, UN EN-TÊTE NE COLLE PAS (2026-08-29).
   `overflow-x: auto` force la spec à mettre `overflow-y` à `auto` lui aussi — on
   le mesure : le cadre rend bien `overflow-y: auto` alors que personne ne l'a
   écrit. Le cadre devient donc le conteneur de défilement le plus proche, et
   l'en-tête collant se rattache À LUI au lieu de la fenêtre : il se fige à
   `--h-collant` du haut du TABLEAU et vient flotter par-dessus la deuxième
   ligne, en plein milieu des données. Vu à l'étape 1 sur le plan existant
   extrait, l'en-tête apparaissait entre la ligne 2 et la ligne 3.
   Le même piège avait été corrigé sur le seul tableau du plan préventif la
   veille, en supprimant son cadre ; les huit autres tableaux le portaient
   encore. Tant qu'un tableau défile horizontalement, son en-tête ne colle pas —
   un en-tête au milieu des lignes est pire qu'un en-tête qu'on perd en
   défilant : le premier ment sur ce qu'il surmonte.

   ⚠️ `.table-scroll table th` ET PAS `.table-scroll th` : les deux se valent en
   spécificité (une classe + un élément), et `.plan-table th` comme
   `.decompo-table th` sont écrits PLUS BAS dans ce fichier — ils gagnaient donc
   à égalité, et la règle ci-dessous ne servait à rien. Aucune erreur, aucun
   avertissement : simplement une règle qui a l'air d'être là. Le sélecteur
   descendant supplémentaire la fait passer devant, où qu'elle soit écrite. */
.table-scroll table th { position: static; }

/* 🔴 …SAUF SI LE CADRE DEVIENT UNE FENÊTRE (2026-09-12, demandé par Pascal sur
   l'AMDEC : « quand on fait du scroll down, on garde le bandeau de colonnes du
   haut »). Le diagnostic de 2026-08-29 ci-dessus reste JUSTE, et c'est lui qui
   donne la solution : l'en-tête se rattache au CADRE. Il suffit donc que le
   cadre soit ce à quoi on veut se rattacher — une fenêtre de hauteur bornée, et
   non une boîte aussi haute que ses cent lignes.

   C'est exactement le remède déjà écrit pour la vue complète de l'étape 5
   (`.cadre-defilant`, plus bas). ⚠️ CE N'EST PAS UNE DEUXIÈME SOURCE : les deux
   règles disent la même chose à deux endroits parce que `.cadre-defilant` est
   posé sur un cadre SANS `table-scroll` (voir `etape5.js:3134`) et ne peut donc
   pas annuler `.table-scroll table th`. Le jour où l'étape 5 passera par
   `table-scroll`, les deux se rejoignent.

   ⚠️ LA HAUTEUR N'EST PAS DEVINÉE. `--h-collant` est MESURÉE au chargement et au
   redimensionnement par `nav-etapes.js` (bandeau des étapes + barre d'actions) :
   sur un écran large la barre d'actions tient sur une ligne, sur un portable
   elle en prend trois, et une valeur écrite en dur ici aurait mangé deux cents
   pixels de tableau sur l'un ou fait passer l'en-tête sous le bandeau sur
   l'autre. Les 16 px sont la respiration sous le cadre, pas une marge de
   sécurité sur une mesure incertaine.

   ⚠️ ET LA BARRE DE DÉFILEMENT HORIZONTALE REVIENT SOUS LES YEUX. Elle vivait au
   pied d'un tableau de onze mille pixels de haut — c'est ce qui avait obligé à
   ajouter les boutons ◀ ▶ de `tables.js`. Dans une fenêtre bornée, elle est
   toujours visible. Les boutons restent : ils sautent d'une vue de colonnes à
   l'autre, ce qu'une barre ne fait pas. */
.table-scroll.cadre-borne {
  overflow: auto;
  max-height: calc(100vh - var(--h-collant, 245px) - 16px);
}
.table-scroll.cadre-borne table thead th { position: sticky; top: 0; z-index: 3; }

/* ============================================================
   CLÔTURE D'UNE ÉTAPE — lire, prendre en compte, valider, passer à la suite
   (2026-09-12, `public/validation.js`)
   ============================================================ */
/* 🔴 EN BAS, ET QUI SE VOIT. Le geste est le même sur les étapes 2 à 6 : il doit
   donc avoir la même tête partout, et se distinguer du contenu au-dessus sans
   crier. Un cadre, un fond légèrement détaché, et de l'air. */
.etape-validation {
  margin: 24px 0 48px;
  padding: 16px 20px;
  border: 1px solid var(--bord); border-radius: var(--r-m);
  background: var(--fond);
}
.etape-validation-titre {
  margin: 0 0 12px; font-size: var(--txt-xl); font-weight: 600;
}
/* Ce qui EMPÊCHE : le rouge du produit, réservé à ce qui bloque vraiment.
   ⚠️ Pas de rouge sur le décompte informatif en dessous — user le rouge sur un
   geste sans conséquence le rend invisible le jour où il compte. */
.etape-validation-bloquants {
  border-left: 3px solid var(--erreur);
  padding: 8px 0 8px 12px; margin-bottom: 12px;
}
.etape-validation-bloquants-titre { margin: 0 0 6px; font-weight: 600; }
.etape-validation-bloquants ul { margin: 0; padding-left: 20px; }
.etape-validation-bloquants li { margin: 2px 0; }
.etape-validation-point { font-weight: 500; }
/* Les identifiants des lignes fautives : lisibles, mais en retrait — c'est le
   POINT qui compte d'abord, les numéros servent à y aller. */
.etape-validation-lignes { color: var(--texte-2); font-size: var(--txt-l); }
/* Ce qui est SIGNALÉ sans empêcher : l'orange « à vérifier » — le MÊME que
   celui des badges du tableau (`--verifier`, posé le 2026-09-11), et pas
   l'ambre du seuil Renforcé. Le décompte du bas et les badges du haut
   parlent de la même chose : ils portent la même couleur. Jamais le rouge —
   user le rouge sur ce qui ne bloque pas le rend invisible là où il compte. */
.etape-validation-averti {
  margin: 0 0 12px; padding: 8px 0 8px 12px;
  border-left: 3px solid var(--verifier);
}
.etape-validation-note { color: var(--texte-2); font-weight: 400; }
.etape-validation-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
/* Le bouton du geste : large, et de la couleur d'une action principale. Il est
   GRISÉ tant qu'un point bloque — et la raison est écrite juste au-dessus, pas
   seulement en infobulle : un bouton gris qui ne dit pas pourquoi est une
   impasse (Pascal, 2026-09-11, sur un autre bouton). */
.btn-valider { width: auto; margin: 0; padding: 8px 20px; font-size: var(--txt-xl); }
.btn-valider:disabled { cursor: not-allowed; }
/* Une fois validée : le geste est FAIT, il ne se represse pas. Vert, parce que
   c'est la couleur de « fait » dans ce produit — décision du 2026-09-11. */
.btn-valide-fait {
  background: var(--succes-fond); color: var(--succes-texte);
  border: 1px solid var(--succes);
}
/* La porte vers l'étape suivante : un LIEN, pas un bouton. Elle ne fait rien —
   elle emmène. */
.etape-validation-suivante { font-size: var(--txt-xl); font-weight: 500; }

/* --- Navigation horizontale d'un tableau large ---------------- */
/* La barre de défilement du navigateur est SOUS le tableau : sur cent cinquante
   lignes, atteindre les dernières colonnes demandait de descendre en bas de
   page, puis de remonter. Ces boutons vivent au-dessus, là où on lit. */
.table-nav {
  display: flex; align-items: center; gap: 8px;
  margin-top: 12px; padding: 6px 2px 0;
}
.table-nav button {
  width: auto; margin: 0; padding: 4px 12px; font-size: var(--txt-xl); line-height: 1.2;
}
.table-nav button:disabled { opacity: 0.35; cursor: default; }
.table-nav-astuce { font-size: var(--txt-m); color: var(--texte-3); }
/* Le tableau se recolle à sa barre quand elle est là. */
.table-nav + .table-scroll { margin-top: 4px; }
.plan-table { width: 100%; border-collapse: collapse; font-size: var(--txt-l); }
.plan-table th, .plan-table td {
  border-bottom: 1px solid var(--bord); padding: 6px 8px;
  text-align: left; vertical-align: top;
}
/* Les en-têtes collent SOUS la barre d'actions, pas dessous au sens propre :
   `--h-collant` = bandeau des étapes + barre d'actions, mesurées par
   nav-etapes.js. Avec `top: 0` ils glisseraient DERRIÈRE la barre et l'on
   perdrait le nom des colonnes au premier défilement. */
.plan-table th {
  background: var(--fond); font-weight: 600; font-size: var(--txt-m);
  position: sticky; top: var(--h-collant, 100px); z-index: 2;
}
.plan-table tbody tr:hover { background: var(--surface); }
/* Une ligne complétée par l'expert se distingue d'un coup d'œil : c'est ce qui
   permet de voir où on en est sur trente activités. */
.ligne-enrichie { background: var(--surface); }
.cell-input { padding: 4px 6px; font-size: var(--txt-l); border-radius: var(--r-s); min-width: 70px; }

/* ------------------------------------------------------------
   Fiabilité terrain — MTBF réel et jauge de benchmark (étape 1)
   ------------------------------------------------------------ */
/* Le chiffre principal est gros parce qu'il est le résultat : tout le reste de
   la carte n'existe que pour le rendre contestable. */
.mtbf-vedette { margin: 4px 0 12px; font-size: var(--txt-2xl); }
.mtbf-vedette strong { font-size: var(--txt-chiffre); color: var(--action); }

.mtbf-chiffres { list-style: none; padding: 0; margin: 0 0 16px; display: flex;
  flex-wrap: wrap; gap: 8px 28px; font-size: var(--txt-l); }
.mtbf-chiffres strong { font-size: var(--txt-2xl); }

/* La jauge occupe la largeur disponible mais ne grossit pas indéfiniment :
   au-delà, le curseur se lit moins bien, pas mieux. */
.jauge { display: block; width: 100%; max-width: 640px; height: auto; margin: 6px 0 2px; }
.jauge-tick { stroke: var(--texte); stroke-width: 2; }
.jauge-tick-median { stroke: var(--texte); stroke-width: 2; }
.jauge-graduation { fill: var(--texte-2); font-size: var(--txt-s); }
.jauge-graduation-median { fill: var(--texte); font-weight: 700; }
.jauge-borne { fill: var(--texte-2); font-size: var(--txt-s); }
.jauge-unite { font-style: italic; }
/* Le curseur porte la couleur du VERDICT, pas celle du dégradé sous lui : hors
   panel il sortirait de la barre et prendrait une teinte trompeuse. */
.jauge-curseur { stroke: var(--surface); stroke-width: 2.5; }
.jauge-curseur.niveau-critique  { fill: var(--erreur); }
.jauge-curseur.niveau-faible    { fill: var(--alerte); }
.jauge-curseur.niveau-bon       { fill: var(--succes); }
.jauge-curseur.niveau-excellent { fill: var(--succes); }

/* La phrase de verdict — celle qui sera reprise devant le client. */
.mtbf-verdict { margin: 8px 0 2px; font-size: var(--txt-xl); display: flex;
  align-items: center; gap: 8px; flex-wrap: wrap; }
.pastille { width: 11px; height: 11px; border-radius: var(--r-cercle); flex: none; }
.pastille.niveau-critique  { background: var(--erreur); }
.pastille.niveau-faible    { background: var(--alerte); }
.pastille.niveau-bon       { background: var(--succes); }
.pastille.niveau-excellent { background: var(--succes); }
.niveau-texte-critique  { color: var(--erreur); }
.niveau-texte-faible    { color: var(--alerte); }
.niveau-texte-bon       { color: var(--succes); }
.niveau-texte-excellent { color: var(--succes); }

/* ------------------------------------------------------------
   📊 Le benchmark de fiabilité — ① déposer, ② calculer, ③ situer (étape 1)
   ------------------------------------------------------------
   Repris du POC le 2026-09-11 : la carte se lit dans l'ordre du travail. Les
   trois titres numérotés portent cet ordre ; ils sont plus petits que le titre
   de carte parce qu'ils en sont les temps, pas des sections voisines. */
.bm-etape { margin: 18px 0 8px; font-size: var(--txt-xl); font-weight: 700; color: var(--texte-2); }
.bm-etape-note { font-weight: 400; color: var(--texte-3); }
/* La zone de dépôt de l'historique ne vit plus dans une grille : elle prend la
   largeur de la carte, sans s'étirer au-delà de ce qu'une liste de fichiers lit. */
#bmDepot .dropzone { max-width: 620px; }
/* La taille d'un fichier ne se coupe pas : « (31 / Ko) » sur deux lignes, sous un
   nom long, se lisait comme deux informations. */
.file-list li > .muted { white-space: nowrap; }

/* ② — le régime rappelé, les heures saisies à la main et le bouton, sur UNE
   ligne qui se replie sur un écran étroit. */
.bm-calcul {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end;
  padding: 12px 14px; background: var(--fond); border: 1px solid var(--bord);
  border-radius: var(--r-m);
}
.bm-base { margin: 0; max-width: 300px; font-size: var(--txt-m); line-height: 1.45; color: var(--texte-2); }
.bm-champ { display: flex; flex-direction: column; max-width: 230px; }
.bm-champ label { margin: 0 0 4px; font-size: var(--txt-m); }
.bm-champ input { text-align: center; padding: 8px 10px; }
.bm-champ-court { max-width: 150px; }
/* ⚠️ `width: auto` ET `margin: 0` : la règle générique `button` donne à tout
   bouton la pleine largeur et une marge haute de 18 px — posé tel quel dans
   cette rangée, il la casserait en deux. */
.bm-bouton, .bm-reset { width: auto; margin: 0; padding: 9px 16px; font-size: var(--txt-l); }

/* Le MTBF réel, résultat de ② — fond d'information, comme au POC. */
.bm-reel {
  margin: 14px 0 4px; padding: 12px 14px; border-radius: var(--r-m);
  background: var(--info-fond); border: 1px solid var(--surlignage);
}
.bm-reel-titre { font-weight: 700; color: var(--info); margin-bottom: 6px; }
.bm-concretement { margin: 0 0 6px; font-size: var(--txt-xl); }
.bm-reel-detail { margin: 0; font-size: var(--txt-m); color: var(--texte-2); }

/* ③ — la famille retenue, en pastille, et la fiabilité de la référence. */
.bm-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 12px; font-size: var(--txt-l); }
.bm-famille {
  display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px;
  border-radius: var(--r-pilule); background: var(--fond); border: 1px solid var(--bord);
  font-weight: 600;
}
/* La lecture fiabiliste de la famille : ambre, parce qu'elle avertit sur la
   MAILLE du chiffre — ce qu'il compte et ce qu'il ne compte pas. */
.bm-lecture {
  margin: 0 0 14px; padding: 11px 14px; border-radius: var(--r-m);
  background: var(--alerte-fond); border: 1px solid var(--alerte);
  color: var(--alerte-texte); font-size: var(--txt-l); line-height: 1.5;
}
.bm-lecture-titre { font-weight: 700; margin-bottom: 5px; }

.bm-unites { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 10px; }
.bm-unites-titre { font-size: var(--txt-m); font-weight: 600; color: var(--texte-2); }
.bm-bascule { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--bord-2); border-radius: var(--r-m); }
.bm-unite-btn {
  width: auto; margin: 0; padding: 5px 14px; border-radius: var(--r-s);
  background: transparent; color: var(--texte-2); font-size: var(--txt-m); font-weight: 600;
}
.bm-unite-btn:hover { background: var(--survol-action); }
.bm-unite-btn.actif, .bm-unite-btn.actif:hover { background: var(--action); color: var(--sur-couleur); }
.bm-unite-btn:disabled { cursor: not-allowed; }
.bm-note { font-size: var(--txt-s); color: var(--texte-3); font-style: italic; }

/* La jauge : la barre, les graduations et le repère sont en SVG — la CSP
   interdit le `style=` qui positionnait le curseur au POC. Les bornes en clair
   restent en HTML, sous la barre, pour se replier sur un écran étroit. */
.bm-jauge .jauge { max-width: none; margin: 4px 0 0; }
.bm-jauge-inactive { opacity: .35; }
.bm-repere { fill: var(--texte); }
.bm-point { fill: var(--texte); stroke: var(--surface); stroke-width: 3; }
.bm-bornes { display: flex; justify-content: space-between; align-items: center; gap: 8px;
  font-size: var(--txt-s); color: var(--texte-3); margin-bottom: 10px; }
.bm-echelle { font-style: italic; text-align: center; }

.bm-verdict {
  margin: 10px 0 16px; padding: 10px 14px; border-radius: var(--r-m);
  background: var(--fond); border: 1px solid var(--bord); font-size: var(--txt-l); line-height: 1.5;
}
.bm-verdict-ligne { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.bm-verdict-detail { margin-top: 2px; font-size: var(--txt-m); color: var(--texte-3); }
/* Le verdict attend la réponse du serveur après une modification de la
   fourchette : on l'estompe plutôt que de laisser lire l'ancien comme le neuf. */
.bm-attente { opacity: .5; }

.bm-valeurs { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.bm-unite-lbl { font-weight: 400; color: var(--texte-3); }
.bm-aide { margin: 10px 0 0; font-size: var(--txt-s); color: var(--action); font-style: italic; }
/* Un bouton qui se présente comme un lien : il ouvre un encadré sur place,
   il ne navigue nulle part — un `<a href="#">` aurait menti sur ce qu'il fait. */
.bm-lien {
  width: auto; margin: 0 0 0 8px; padding: 0; background: none; border: none;
  color: var(--action); font-size: var(--txt-s); text-decoration: underline; cursor: pointer;
}
.bm-lien:hover { background: none; color: var(--action-survol); }
.bm-sources {
  margin-top: 8px; padding: 10px 14px; background: var(--fond); border: 1px solid var(--bord);
  border-radius: var(--r-m); font-size: var(--txt-s); color: var(--texte-2);
}
.bm-sources ul { margin: 4px 0 0; padding-left: 18px; }
.bm-sources a { color: var(--action); }
.bm-sources-notes { display: block; margin-top: 6px; font-style: italic; }
/* Le renvoi posé sous la grille des dépôts : l'historique n'y est plus, et il
   faut le dire là où on le cherchera d'abord. */
.bm-renvoi { margin: 10px 0 0; font-size: var(--txt-l); color: var(--texte-2); }

/* L'explication est en liste de définitions : chaque ligne est un « d'où sort
   ce chiffre », et se lit indépendamment des autres. */
.mtbf-explication { display: grid; grid-template-columns: 220px minmax(0, 1fr);
  gap: 4px 16px; margin: 10px 0 0; font-size: var(--txt-l); }
.mtbf-explication dt { color: var(--texte-2); }
.mtbf-explication dd { margin: 0; }
@media (max-width: 640px) {
  .mtbf-explication { grid-template-columns: 1fr; }
  .mtbf-explication dd { margin-bottom: 6px; }
}

/* Badge de fraîcheur — « ce résultat a été calculé avant que ses entrées ne
   changent ». Ambre et non rouge : ce n'est pas une erreur, c'est un décalage
   à arbitrer. Le rouge est réservé à ce qui a échoué. */
/* Le tableau des comptes.
   ⚠️ `width: auto` sur le menu de rôle : la règle générale donne `width: 100%`
   à tout `select`, ce qui le réduisait à la largeur de sa colonne — « Fia⌄ » au
   lieu de « Fiabiliste ». Une largeur minimale garantit qu'on lise le rôle, et
   c'est le conteneur `.table-scroll` qui absorbe le débordement.
   `nowrap` sur les cellules d'état : « Mot de passe provisoire » se coupait au
   milieu de sa pastille. Rien de tout cela ne se voit dans la source. */
.table-comptes select { width: auto; min-width: 130px; padding: 6px 8px; font-size: var(--txt-l); }
.table-comptes td { vertical-align: middle; }
.table-comptes .badge-perime { white-space: nowrap; }

.badge-perime {
  margin: 0 0 12px; padding: 10px 14px; border-radius: var(--r-m);
  background: var(--alerte-fond); border: 2px solid var(--alerte); color: var(--alerte-texte);
  font-size: var(--txt-l); line-height: 1.5;
}
.badge-perime .muted { color: var(--alerte-texte); }

/* ------------------------------------------------------------
   Navigation entre étapes
   ------------------------------------------------------------ */
.etapes { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.etape-lien {
  padding: 6px 12px; border-radius: var(--r-pilule); font-size: var(--txt-l);
  border: 1px solid var(--bord-2); color: var(--action); text-decoration: none; background: var(--surface);
}
.etape-lien:hover { background: var(--fond); }
.etape-courante { background: var(--surlignage); border-color: var(--surlignage); color: var(--action); font-weight: 600; }


/* ============================================================
   ÉTAPE 2 — décomposition fonctionnelle
   ============================================================ */
/* ⚠️ PLEINE LARGEUR, contrairement au reste du produit. `.container` plafonne à
   860 px, ce qui convient à un formulaire ; ici il y a DIX colonnes dont trois
   de texte long, et la compression tronquait tout. La lisibilité de ce tableau
   est la fonction principale de l'écran, pas un agrément. */
.pleine-largeur { margin: 16px; }

.titre-etape {
  margin-left: 14px; padding: 4px 12px; border-radius: var(--r-pilule);
  background: var(--alerte-fond); border: 1px solid var(--alerte); color: var(--erreur);
  font-size: var(--txt-l); font-weight: 600;
}

/* La barre d'actions : tout sur une ligne, les compteurs au milieu. Sur
   soixante-dix lignes, on y revient sans cesse — elle doit rester au même
   endroit et ne pas se réorganiser au fil des clics (les boutons se désactivent,
   ils ne disparaissent pas). */
/* 🔴 ELLE RESTE VISIBLE PENDANT L'ÉDITION (2026-08-28). « ↶ Annuler la saisie »
   vit ici ; en éditant la ligne 60 d'un tableau de 115, cette barre était à
   trois écrans au-dessus. Le bouton s'activait bien — et personne ne le voyait,
   ce qui revient exactement au même que s'il n'existait pas.
   `--h-nav` est mesurée par nav-etapes.js : les deux bandeaux passent sur deux
   lignes quand la fenêtre est étroite, donc leur hauteur ne peut pas être écrite
   en dur. Le repli couvre le cas où le script n'a pas encore mesuré. */
.barre-actions {
  position: sticky; top: var(--h-nav, 48px); z-index: 900;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--surface); padding: 10px 14px; border-radius: var(--r-l);
  box-shadow: var(--ombre-1); margin-bottom: 12px;
}
.barre-actions button, .barre-actions .btn-secondary {
  width: auto; margin: 0; padding: 7px 13px; font-size: var(--txt-l);
}
.barre-actions button:disabled { opacity: 0.4; cursor: default; }
/* 🔴 SANS CETTE LIGNE, `barre.hidden = true` NE CACHE RIEN (2026-09-02). Le
   `display: flex` ci-dessus est une règle d'auteur : elle l'emporte sur le
   `display: none` que le navigateur attache à `[hidden]`. L'attribut est bien
   posé, l'élément reste visible, et rien ne le signale — deuxième occurrence
   après `#logoDelete`, cf. le commentaire en tête de ce fichier. Vu à l'écran
   pendant la première vraie génération : deux boutons flottant au-dessus d'un
   écran vide, exactement le cadre vide que le lot 7a avait refusé. */
.barre-actions[hidden] { display: none; }
.pousse { flex: 1; }
/* L'export Word (étape 6, lot 10c) : un choix de template et un LIEN de
   téléchargement, habillé comme un bouton secondaire. Le lien passe en
   pointillés quand des saisies ne sont pas enregistrées : le papier ne les
   aurait pas. */
.export-word { display: inline-flex; align-items: center; gap: 6px; font-size: var(--txt-m); }
.export-word select { width: auto; margin: 0; padding: 5px 8px; font-size: var(--txt-m); max-width: 220px; }
.barre-actions a.btn-secondary { text-decoration: none; display: inline-block; line-height: 1.2; }
.barre-actions a.lien-perime { outline: 1px dashed var(--alerte-texte); color: var(--alerte-texte); }
.stats { font-size: var(--txt-l); color: var(--texte-2); padding: 0 6px; }

/* --- Le tableau --------------------------------------------- */
.decompo-table {
  width: 100%; border-collapse: collapse; font-size: var(--txt-l); background: var(--surface);
}
/* ⚠️ `table-layout: fixed` OU LES LARGEURS NE SERVENT À RIEN. En disposition
   automatique — la valeur par défaut — le navigateur traite `width` comme une
   suggestion et élargit selon le contenu : la colonne « ID », déclarée à 100 px
   pour six caractères, en occupait le double, pendant que la tâche se serrait.
   Réservé au plan préventif : les autres écrans partagent `.decompo-table` et
   n'ont pas été relus sous cette contrainte. */
.table-plan { table-layout: fixed; }
/* 🔴 « N'IMPORTE OÙ » VAUT POUR LES CELLULES, PAS POUR LES TITRES (2026-08-29).
   Cette règle existe pour qu'un libellé de tâche de quatre-vingts caractères ne
   déborde pas de sa colonne. Appliquée aussi aux en-têtes, elle cassait « Niv. »
   — 23 px de texte dans une colonne de 26 — en « N », « IV », « . » sur trois
   lignes, ce qui imposait 79 px de hauteur à TOUTE la ligne d'en-tête. Un titre
   de colonne est court et connu : s'il ne tient pas, c'est la largeur qu'il faut
   corriger, pas le mot qu'il faut couper. */
.table-plan td { overflow-wrap: anywhere; }
.table-plan th { overflow-wrap: normal; }
/* Les deux colonnes les plus étroites paient leur padding plus cher que leur
   contenu : 20 px de marge pour 23 px de texte. */
.table-plan th.col-niveau, .table-plan th.col-signal { padding-left: 4px; padding-right: 4px; }
/* Le cadre du plan ne défile PAS horizontalement : `overflow-x: auto` force la
   spec à mettre `overflow-y` à `auto` lui aussi, et les en-têtes collants se
   rattachent alors au cadre au lieu de la fenêtre — ils réapparaissent au milieu
   des lignes. En largeurs fixes totalisant 100 %, il n'y a rien à faire défiler. */
.table-plan-cadre { margin-top: 12px; }

/* ⚠️ CES LARGEURS TOTALISENT 100 %, ET IL LE FAUT. En `table-layout: fixed`
   avec `width: 100 %`, l'espace non attribué est redistribué AU PRORATA sur
   toutes les colonnes : déclarer « ID » à 84 px et laisser 19 % non répartis lui
   en rendait le double. Une largeur qui ne fait pas partie d'un tout n'est pas
   une largeur, c'est une proportion déguisée.
   Toute colonne ajoutée ou retirée oblige donc à refaire la somme. */
.table-plan .col-signal   { width: 2%; }
.table-plan .col-id       { width: 5%; }
/* Le niveau tient en deux caractères (N1/N2/N3) — mais son TITRE en demande
   quatre : à 3 % il mesurait 26 px pour 23 px de texte plus 20 px de marge. */
.table-plan .col-niveau   { width: 4%; }
/* L'ensemble se répète à l'identique sur des dizaines de lignes : il situe,
   il ne s'épelle pas. */
.table-plan .col-ensemble { width: 11%; }
.table-plan .col-composant{ width: 13%; }
/* La tâche est ce qu'on vient lire : elle prend le plus large. */
.table-plan .col-tache    { width: 21%; }
.table-plan .col-frequence{ width: 11%; }
.table-plan .col-quifait  { width: 11%; }
/* La SOURCE ne s'omet pas : « d'où vient cette tâche » est ce qu'on montre au
   client, et c'est l'axe traçabilité du produit. */
.table-plan .col-source   { width: 9%; }
/* 13 % et non 14 : le point rendu au niveau devait être pris quelque part, et
   c'est la somme à 100 % qui décide — pas la place disponible, qui n'existe pas. */
.table-plan .col-decision { width: 13%; }
.decompo-table th {
  background: var(--fond); border-bottom: 2px solid var(--bord-2);
  padding: 9px 10px; text-align: left; vertical-align: bottom;
  font-size: var(--txt-s); font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; color: var(--texte-2);
  /* Ils collent SOUS la barre d'actions, pas en haut de la fenêtre :
     `--h-collant` = bandeau des étapes + barre d'actions, mesurées par
     nav-etapes.js. Avec `top: 0`, l'en-tête glissait derrière la barre et on
     perdait le nom des colonnes au premier défilement. */
  position: sticky; top: var(--h-collant, 100px); z-index: 1;
}
.decompo-table td {
  border-bottom: 1px solid var(--bord); padding: 9px 10px; vertical-align: top;
}

/* ⚠️ LE NIVEAU SE LIT DANS LA COULEUR — il n'a pas de colonne à lui.
   Bleu = système, orange = sous-système, normal = composant. C'est ce qui permet
   de retrouver la structure d'un coup d'œil sur quatre-vingts lignes, et c'est
   exactement ce que faisait le POC. */
.ligne-systeme { background: var(--surlignage); }
.ligne-systeme td { color: var(--action); font-weight: 600; }
.ligne-sousSysteme { background: var(--alerte-fond); }
.ligne-sousSysteme td { color: var(--erreur); font-weight: 600; }
.decompo-table tbody tr:hover { background: var(--fond); }
.ligne-systeme:hover { background: var(--surlignage); }
.ligne-sousSysteme:hover { background: var(--sur-couleur); }

/* --- Les cellules éditables ---------------------------------- */
/* ⚠️ `contenteditable`, PAS un champ de saisie. Un <input> de 130 px tronquait
   les libellés et rendait illisible une observation qui recopie trois lignes de
   préconisation constructeur. Ici le texte prend la place qu'il lui faut et se
   lit comme du texte ; le cadre n'apparaît qu'au survol et à la saisie. */
/* La cellule qu'un retour en arrière vient de restaurer. Un Ctrl+Z dont on ne
   voit pas l'effet est pire que pas de Ctrl+Z : on ne sait pas ce qui a changé,
   et on recommence. Elle est ramenée à l'écran ET mise en évidence. */
/* ⚠️ INDIGO, ET PAS AMBRE. Le marquage « modifiée » est passé au jaune : garder
   l'ambre ici aurait donné deux signaux jaunes voisins, l'un permanent et
   l'autre fugace, impossibles à distinguer au moment même où l'on cherche à
   voir CE QUI VIENT d'être restauré. Deux repères qui se ressemblent n'en font
   qu'un — et celui qui reste est le mauvais. */
.cellule-restauree {
  animation: cellule-restauree-eclat 1.8s ease-out;
  outline: 2px solid var(--action); outline-offset: -2px;
}
@keyframes cellule-restauree-eclat {
  0% { background: var(--surlignage); }
  100% { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .cellule-restauree { animation: none; background: var(--surlignage); }
}

/* ⚠️ LA CELLULE MODIFIÉE DEPUIS LE CHARGEMENT. Sur cent cinquante lignes et
   vingt colonnes, retrouver ce qu'on a touché avant d'enregistrer était
   impossible autrement. Le marquage disparaît à l'enregistrement — et c'est
   voulu : le document affiché redevient alors celui du serveur, « modifié » n'a
   plus de sens. (Demande de Pascal, 2026-08-27.)

   Le repère porte sur la BARRE DE GAUCHE et non sur le seul fond : les lignes
   ont déjà une couleur de criticité, et un fond de plus s'y noierait. */
/* Jaune, décision Pascal du 2026-08-27 : le bleu était trop proche des couleurs
   d'accent de l'interface (boutons, barre du voile) pour se lire comme un
   signal. Le jaune ne sert nulle part ailleurs. */
.cellule-modifiee {
  background: var(--cellule-modifiee);
  box-shadow: inset 3px 0 0 var(--alerte-texte);
}

.cellule-editable { cursor: text; border-radius: var(--r-xs); }
.cellule-editable:hover { background: var(--survol-action); }
.cellule-editable:focus {
  outline: 2px solid var(--action); outline-offset: -2px; background: var(--surface);
}

.col-coche { width: 30px; }
.col-actions { width: 110px; white-space: nowrap; }
.col-id { width: 84px; }
.col-moyenne { width: 12%; }
.col-large { width: 15%; }
.col-tres-large { width: 22%; }

/* ⚠️ EN PIXELS, PAS EN POURCENTAGE, et c'est tout l'intérêt. Ces deux colonnes
   portent des LISTES FERMÉES aux libellés longs, dont plusieurs ne diffèrent
   que par leur dernier mot. Un pourcentage de 12 % se fait écraser par les
   vingt-trois autres colonnes du tableau : les libellés se tronquent au même
   endroit et deux décisions opposées deviennent indiscernables. Un `min-width`
   en pixels tient la largeur qu'il faut au texte, quel que soit le nombre de
   colonnes voisines. Les libellés eux-mêmes viennent du serveur et ne sont
   recopiés nulle part — pas même ici, où ils finiraient par être périmés.
   (Signalé par Pascal le 2026-08-27, sur la première AMDEC réelle du socle.) */
.col-type { min-width: 172px; }
.col-revision { min-width: 148px; }

/* --- Étape 4 : les colonnes de l'analyse des pannes ----------- */
/* ⚠️ EN PIXELS, comme ci-dessus, et pour la même raison : dix-sept colonnes se
   partageaient des pourcentages, si bien que l'identifiant « P-001 » recevait
   la même largeur qu'un sous-ensemble de quarante caractères. Les quatre
   premières colonnes portent des valeurs COURTES et de longueur connue — un
   identifiant, un numéro d'ordre GMAO, une date, une durée : leur donner la
   place qu'il faut, et pas davantage, rend celle-ci aux colonnes de texte.
   (Signalé par Pascal le 2026-08-27, sur la première RCA réelle du socle.) */
.col-rca-id { min-width: 62px; width: 62px; }
.col-rca-wo { min-width: 118px; }
.col-rca-date { min-width: 92px; }
.col-rca-duree { min-width: 56px; }
/* Les colonnes de texte libre : c'est là que se lit l'analyse. */
.col-rca-sousens { min-width: 165px; }
.col-rca-composant { min-width: 175px; }
.col-rca-defaut { min-width: 165px; }
/* Deux LISTES FERMÉES aux libellés longs. Écrasées, elles n'affichaient plus
   que deux ou trois lettres — « En », « Ma », « Su » — c'est-à-dire rien.
   La cause racine commande le Pareto : elle doit se lire, pas se deviner. */
.col-rca-mode { min-width: 195px; }
.col-rca-cause { min-width: 210px; }

.identifiant {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--txt-s); font-weight: 700;
}
/* Le triplet vient d'être modifié : le niveau affiché n'est plus forcément le
   bon, et c'est le serveur qui tranchera. On le signale sans le recalculer. */
.identifiant-provisoire {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--txt-s); font-style: italic; color: var(--alerte-texte); font-weight: 400;
}

/* 🔴 `width: auto` ET `margin: 0` MANQUAIENT (corrigé le 2026-09-11, signalé
   par Pascal sur l'AMDEC de Colinet : « des taches grises dans le texte de
   Fonction »). La règle générique `button` donne à tout bouton la pleine
   largeur : chacun des quatre ▲ ▼ ✕ ⧉ prenait la largeur de la cellule
   Actions, et `nowrap` les alignait quand même — la rangée faisait 380 px dans
   une colonne de 110. ▼ tombait sur l'ID, ✕ sur le Composant, ⧉ en plein
   milieu du texte de la Fonction : c'étaient les « taches grises ». */
.btn-icone {
  width: auto; margin: 0;
  background: none; border: 1px solid transparent; border-radius: var(--r-s);
  padding: 1px 5px; cursor: pointer; font-size: var(--txt-m); color: var(--texte-3);
}
.btn-icone:hover:not(:disabled) { border-color: var(--bord-2); background: var(--surface); color: var(--texte); }
.btn-icone:disabled { opacity: 0.25; cursor: default; }

.select-nature {
  width: 100%; padding: 4px 6px; font-size: var(--txt-m); border-radius: var(--r-s);
  border: 1px solid var(--bord-2); background: var(--surface);
}

/* Les corrections automatiques sont repliées : consultables, pas encombrantes.
   Une décomposition en compte souvent une dizaine. */
.anomalies { margin: 8px 0; font-size: var(--txt-l); }
.anomalies summary { cursor: pointer; color: var(--alerte-texte); }
.anomalies ul { margin: 8px 0 0 18px; color: var(--texte-2); }

/* ============================================================
   BANDEAU DES NEUF ÉTAPES — partagé par tous les écrans
   ============================================================ */
/* Il reste visible au défilement : sur un tableau de quatre-vingts lignes, on
   veut pouvoir passer à l'étape suivante sans remonter. */
.nav-etapes {
  position: sticky; top: 0; z-index: 1000;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  background: var(--surface); border-bottom: 1px solid var(--bord-2);
  box-shadow: var(--ombre-1); padding: 9px 16px;
}
.nav-liste { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.nav-accueil {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: var(--r-m); background: var(--fond);
  color: var(--texte); text-decoration: none; font-size: var(--txt-l); font-weight: 600;
  margin-right: 6px; border: 1px solid transparent;
}
.nav-accueil:hover { background: var(--bord-2); }

.nav-etape {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border-radius: var(--r-l);
  background: var(--fond); color: var(--texte-2); text-decoration: none;
  font-size: var(--txt-m); font-weight: 600; border: 1px solid var(--bord-2);
  white-space: nowrap;
}
a.nav-etape:hover { box-shadow: var(--ombre-2); }
.nav-numero {
  display: inline-flex; align-items: center; justify-content: center;
  width: 19px; height: 19px; border-radius: var(--r-cercle);
  background: var(--surface); border: 1px solid var(--bord-2); font-size: var(--txt-s); font-weight: 700;
}

/* --- L'AVANCEMENT donne la couleur du fond -------------------- */
/* Vert = validé par l'expert · ambre = généré mais pas encore validé.
   La distinction compte : un plan de maintenance livré à un client ne doit
   contenir que des étapes validées, et ça doit se voir sans ouvrir chaque écran. */
.avancement-valide { background: var(--succes-fond); color: var(--succes-texte); border-color: var(--succes); }
.avancement-valide .nav-numero { background: var(--succes); color: var(--surface); border-color: var(--succes); }
.avancement-genere { background: var(--alerte-fond); color: var(--alerte-texte); border-color: var(--alerte); }
.avancement-genere .nav-numero { background: var(--alerte-texte); color: var(--surface); border-color: var(--alerte-texte); }

/* L'étape courante prime sur tout le reste : c'est le repère « où suis-je ». */
.nav-courante,
.nav-courante.avancement-valide,
.nav-courante.avancement-genere {
  background: var(--action); color: var(--surface); border-color: var(--action);
  box-shadow: var(--ombre-action);
}
.nav-courante .nav-numero { background: var(--surface); color: var(--action); border-color: var(--surface); }

/* Étape pas encore portée sur le socle : présente mais éteinte. La masquer
   donnerait l'impression d'un outil qui s'arrête là où le portage s'arrête. */
.nav-a-venir { opacity: 0.45; cursor: default; }
/* Étape hors du parcours de l'analyse (AMDEC et RCA en SOP express) : ni en
   retard, ni à venir — simplement pas du voyage. Barrée, pour ne pas se lire
   comme un travail oublié. */
.nav-hors-parcours { opacity: 0.45; cursor: default; text-decoration: line-through; }

/* --- LA FRAÎCHEUR donne la pastille --------------------------- */
.nav-pastille {
  display: inline-block; width: 8px; height: 8px; border-radius: var(--r-cercle);
  background: var(--surlignage); box-shadow: var(--anneau);
}
.pastille-fresh { background: var(--succes); }
.pastille-missing { background: var(--surlignage); }
/* Ambre et clignotante : c'est le seul signal qui demande une ACTION — une
   donnée amont a changé, ce résultat n'est plus synchronisé. */
.pastille-stale { background: var(--alerte); animation: pulse-pastille 1.2s ease-in-out infinite; }
.nav-etape:has(.pastille-stale) { border-color: var(--alerte); }
/* Aucune fraîcheur déclarée : pas de pastille visible plutôt qu'une pastille
   verte qui ferait croire à une vérification qui n'a pas eu lieu. */
.pastille-aucune { background: transparent; box-shadow: none; }
@keyframes pulse-pastille {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .55; transform: scale(.82); }
}

.nav-info {
  margin-left: auto; font-size: var(--txt-m); color: var(--texte-2);
  padding: 4px 10px; background: var(--fond); border-radius: var(--r-s); border: 1px solid var(--bord);
}
.nav-info-erreur { color: var(--erreur); background: var(--erreur-fond); border-color: var(--erreur); }

@media (max-width: 900px) {
  .nav-info { display: none; }
  .nav-etape { padding: 5px 9px; font-size: var(--txt-m); }
  .nav-libelle { display: none; }
}

/* ============================================================
   LE VOILE D'ATTENTE — public/progression.js
   ------------------------------------------------------------
   Les générations durent des MINUTES. Un bouton grisé et muet pendant six
   minutes se lit comme une page plantée : le voile dit qu'il se passe quelque
   chose, depuis combien de temps, et il RESTE OUVERT en cas d'échec pour que
   le message ne passe pas inaperçu.
   ============================================================ */
.voile {
  position: fixed; inset: 0; z-index: 3000;
  background: var(--voile);
  display: flex; align-items: center; justify-content: center;
}
.voile-carte {
  background: var(--surface); border: 1px solid var(--bord); border-radius: var(--r-xl);
  padding: 30px 38px; max-width: 520px;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  box-shadow: var(--ombre-3);
}
.sablier { font-size: var(--txt-geant); animation: sablier-tourne 2.4s ease-in-out infinite; }
.sablier-echec { animation: none; }
@keyframes sablier-tourne {
  0%, 25% { transform: rotate(0deg); }
  50%, 75% { transform: rotate(180deg); }
  100% { transform: rotate(360deg); }
}
.voile-titre { margin: 0; font-size: var(--txt-2xl); font-weight: 600; text-align: center; }
.voile-titre-echec { color: var(--erreur); }
.voile-soustitre { margin: 0; font-size: var(--txt-l); color: var(--texte-2); text-align: center; }
/* Le temps écoulé sous le voile : discret, et vide tant qu'il n'y a rien à
   dire — un compteur à « 0 s » sur une requête d'une seconde est du bruit. */
.voile-chrono { margin: 4px 0 0; font-size: var(--txt-l); color: var(--texte-2); text-align: center;
                font-variant-numeric: tabular-nums; }

/* Ce qu'une décision d'arbitrage exige et que le modèle ne peut pas connaître :
   fréquence, niveau, critère d'acceptation. Empilés, avec leur libellé au-dessus
   — une case sans libellé se remplit au hasard. */
.champ-saisie { display: block; margin: 0 0 6px; }
.champ-saisie span { display: block; font-size: var(--txt-s); margin-bottom: 2px; }
.champ-saisie input, .champ-saisie select { width: 100%; box-sizing: border-box;
  font-size: var(--txt-l); padding: 3px 4px; }

/* Un geste DÉJÀ FAIT, dans une barre où les autres restent à faire.
   ⚠️ LES MÊMES COULEURS QUE `.jalon-fait`, et c'est délibéré : un vert pour un
   jalon franchi et un autre vert pour un geste fait apprendraient à l'œil que
   les deux ne veulent pas dire la même chose. Ils veulent dire la même chose. */
.btn-fait { background: var(--succes-fond); color: var(--succes-texte); border-color: var(--succes); }
.btn-fait:hover:not(:disabled) { background: var(--succes-fond); }

/* Les gestes d'un jalon, dans l'ordre d'exécution. Trois états visuellement
   distincts : fait, à faire, verrouillé. Un verrouillé qui ressemblerait à un
   « à faire » ferait cliquer sur ce qui refusera. */
/* Le geste destructeur, sorti du rang. Il ne voisine plus « Enregistrer » :
   un remplacement complet du plan et une sauvegarde ne se cliquent pas au même
   endroit, et sûrement pas avec le même poids. */
/* L'histogramme de charge : une barre par période, la hauteur en proportion du
   pic. C'est la réponse visuelle à « ça tombe quand ? » — un tableau de 159
   lignes ne la donne pas.
   ⚠️ LES PÉRIODES D'ARRÊT SE DISTINGUENT : une barre haute en août n'est pas un
   problème si c'est là que la ligne s'arrête. */
/* 🔴 LA MAILLE, POSÉE CONTRE LE TABLEAU QU'ELLE COMMANDE (2026-09-01). Dans la
   barre de réglages du haut, elle passait sous la barre d'actions collante dès
   qu'on descendait dans le tableau : le réglage existait, et l'utilisateur ne le
   trouvait plus. Deux boutons plutôt qu'une liste — il n'y a que deux valeurs,
   et une liste cache la seconde derrière un clic. */
.maille-choix {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 14px 0 6px;
}
.maille-titre {
  font-size: var(--txt-s); font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; color: var(--texte-2);
}
.btn-maille {
  border: 1px solid var(--bord-2); background: var(--surface); color: var(--texte);
  border-radius: var(--r-s); padding: 5px 12px; font-size: var(--txt-l); cursor: pointer;
}
.btn-maille:hover { background: var(--bord); }
/* ⚠️ L'ÉTAT ACTIF SE VOIT SANS SURVOL : c'est lui qui dit ce qu'on regarde. */
.btn-maille.actif {
  background: var(--action); border-color: var(--action); color: var(--surface); font-weight: 600;
}

/* 🔴 L'HISTOGRAMME EST UNE LIGNE D'EN-TÊTE DU TABLEAU (2026-09-01). En bloc
   séparé au-dessus, les barres se répartissaient sur toute la largeur pendant
   que le tableau réservait deux colonnes à gauche et une au total : aucune ne
   tombait en face de son mois. Ici, c'est la mise en page du tableau qui aligne,
   et le défilement horizontal — 53 colonnes en maille hebdomadaire — emporte les
   barres avec les colonnes. */
/* ⚠️ CETTE LIGNE NE COLLE PAS, contrairement aux en-têtes. Deux lignes collantes
   à la même hauteur se chevauchent : l'histogramme masquerait le nom des mois au
   premier défilement. Ce sont les LIBELLÉS qu'on doit garder sous les yeux. */
.ligne-histo th {
  position: static; background: var(--surface); border-bottom: 0; padding: 0 2px;
  vertical-align: bottom; text-transform: none; z-index: auto;
}
.histo-legende {
  font-size: var(--txt-s); font-weight: 400; color: var(--texte-2);
  padding: 0 10px 4px !important; white-space: nowrap;
}
/* La barre part du bas : la cellule a une hauteur fixe et son contenu est aligné
   en bas (`vertical-align: bottom`, hérité de la règle ci-dessus). */
.histo-cellule { height: 96px; }
.histo-barre { width: 100%; background: var(--action); border-radius: var(--r-xs) var(--r-xs) 0 0; }
/* ⚠️ UN PIC VOULU N'EST PAS UN PIC SUBI : la période qui porte un arrêt
   technique se distingue, sinon août se lit comme une surcharge. */
.histo-arret { background: var(--erreur); }
.ligne-total td { font-weight: 700; border-top: 2px solid var(--bord-2); background: var(--fond); }

/* 🔴 L'ONGLET OÙ SE FAIT LE TRAVAIL DU JALON COURANT. Les deux barres de l'écran
   parlaient du même parcours sans se répondre : le jalon nommait un travail, et
   il fallait deviner dans quel onglet le trouver. Le point bleu fait le lien.
   ⚠️ IL NE REMPLACE PAS L'ONGLET ACTIF — on peut consulter la charge pendant que
   le travail attend ailleurs, et les deux marques doivent alors coexister. */
.onglet-du-jalon { position: relative; font-weight: 700; }
.onglet-du-jalon::after {
  content: ''; position: absolute; top: 6px; right: 6px;
  width: 7px; height: 7px; border-radius: var(--r-cercle); background: var(--action);
}

/* ⚠️ `display: flex` neutralise le [hidden] du navigateur : sans la règle
   suivante, la zone de régénération resterait affichée alors qu'il n'y a encore
   aucun plan à perdre (2026-09-11). */
.zone-destructive[hidden] { display: none; }
.zone-destructive { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 2rem 0 1rem; padding: 14px 16px;
  background: var(--erreur-fond); border: 1px solid var(--erreur); border-radius: var(--r-m); }
/* ⚠️ CE TEXTE N'EST PAS UNE MENTION LÉGALE — c'est la seule chose qui retient
   la main avant un geste irréversible. En gris 12 px, il se lisait comme une
   note de bas de page : il porte maintenant le poids de ce qu'il annonce. */
/* ⚠️ `flex: 0 1 auto` DEPUIS QU'ELLE VIT DANS LA BARRE D'ACTIONS (2026-09-02).
   Avec `flex: 1` elle poussait « Enregistrer » et « Valider » hors de vue sur
   un écran étroit — une phrase d'avertissement qui cache les boutons qu'elle
   sert à distinguer fait exactement l'inverse de ce qu'on lui demande. */
.perte-annoncee { flex: 0 1 auto; font-size: var(--txt-m); line-height: 1.4;
  color: var(--erreur); font-weight: 500; }
.btn-destructif { background: var(--surface); color: var(--erreur); border: 1px solid var(--erreur);
  font-weight: 600; }

/* 🔴 UN LIEN, PAS UN BOUTON — et c'est tout le propos (2026-09-02, Pascal : « le
   grand ruban a tendance à inviter au clic »). Ce geste OUVRE, il n'exécute
   pas : lui donner l'aspect plein d'un bouton d'action serait mentir sur ce
   qu'il fait, et l'offrir à la main qui vise « Enregistrer ». */
/* ⚠️ Blanc sur blanc, il était invisible (Pascal, 2026-09-03). Il garde sa
   nature de lien — souligné, jamais plein — mais sur un fond rosé et dans un
   cadre : on doit le TROUVER sans que la main y aille toute seule. */
.btn-lien-destructif {
  background: var(--erreur-fond); border: 1px solid var(--erreur); box-shadow: none;
  color: var(--erreur); text-decoration: underline; font-weight: 600;
  padding: 5px 10px; width: auto; margin: 0; border-radius: var(--r-s);
}
.btn-lien-destructif:hover:not(:disabled) { background: var(--erreur-fond); border-color: var(--erreur); }

/* Le ruban d'armement : il prend la place du lien, il ne s'ajoute pas à côté.
   Deux cibles au même endroit à deux instants différents — jamais deux cibles
   en même temps. */
.ruban-armement {
  display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--erreur-fond); border: 1px solid var(--erreur); border-radius: var(--r-m);
  padding: 6px 10px; color: var(--erreur); font-size: var(--txt-m); line-height: 1.4;
}
.ruban-armement[hidden] { display: none; }
.btn-destructif:hover:not(:disabled) { background: var(--erreur-fond); }

.gestes-jalon { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 10px; }
.geste { display: flex; align-items: flex-start; gap: 10px; padding: 8px 10px;
  border-radius: var(--r-s); border: 1px solid transparent; }
.geste-texte { flex: 1; min-width: 0; }
.geste-puce { flex: none; width: 18px; text-align: center; }
.geste-fait       { background: var(--succes-fond); color: var(--succes-texte); }
.geste-a_faire    { border-color: var(--texte-3); }
.geste-verrouille { border-style: dashed; border-color: var(--bord-2); color: var(--texte-2); }
/* Un calcul qui a eu lieu, mais sur un plan qui n'existe plus. Ni vert ni
   neutre : ce serait dire qu'il est fait, ou qu'il ne l'est pas — les deux
   sont faux, et c'est cette confusion qui a coûté une matinée. */
.geste-perime { background: var(--alerte-fond); border-color: var(--alerte); }
.btn-perime { background: var(--alerte-fond); color: var(--alerte-texte); border-color: var(--alerte); }
.btn-perime:hover:not(:disabled) { background: var(--alerte-fond); }
/* Le message d'erreur du serveur, en entier : il porte souvent la seule
   indication exploitable (clé absente, crédit épuisé, décomposition manquante). */
.voile-message-erreur {
  color: var(--erreur); font-size: var(--txt-xl); background: var(--erreur-fond);
  border: 1px solid var(--erreur); border-radius: var(--r-m); padding: 10px 14px;
  white-space: pre-wrap; text-align: left;
}
.voile-barre { display: block; width: 320px; height: 8px; }
.barre-fond { fill: var(--bord); }
.barre-avance { fill: var(--action); }
/* Le lot EN COURS, distinct du lot TERMINÉ. Sans cette nuance, la barre ne dit
   pas si le lot affiché est fait ou en train de se faire — et sur des lots de
   huit minutes, c'est toute la différence entre « ça avance » et « ça a fini ».
   Teinte plus claire que barre-avance : en cours n'est pas acquis. */
.barre-encours { fill: var(--action); }
.barre-encours-anim { animation: barre-pulse 1.6s ease-in-out infinite; }
@keyframes barre-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
/* ⚠️ Le clignotement suit la préférence système : une animation permanente est
   pénible, et handicapante pour qui y est sensible. La couleur, elle, reste. */
@media (prefers-reduced-motion: reduce) {
  .barre-encours-anim { animation: none; }
}
.voile-chrono {
  margin: 0; font-size: var(--txt-l); color: var(--alerte-texte); font-weight: 600;
  font-variant-numeric: tabular-nums; text-align: center;
}
.voile-note { margin: 0; font-size: var(--txt-m); color: var(--texte-3); text-align: center; }
.voile-actions { display: flex; gap: 10px; }
.voile-actions button { width: auto; margin: 0; }

/* Le voile ORDINAIRE — toute requête qui dépasse le délai de courtoisie.
   Plus léger que celui des générations : on attend deux secondes, pas six
   minutes, et le fond reste lisible derrière. */
.voile-discret { background: var(--voile-discret); }
.voile-discret .voile-carte { padding: 20px 30px; gap: 8px; }
.voile-discret .sablier { font-size: var(--txt-chiffre); }
.voile-discret .voile-titre { font-weight: 400; color: var(--texte-2); }

/* ============================================================
   ONGLETS — étapes 3 et 4
   ------------------------------------------------------------
   ⚠️ Le contenu des onglets masqués reste DANS LE DOM (hidden), il n'est pas
   détruit et reconstruit. Sur 201 pannes, reconstruire le tableau à chaque
   changement d'onglet perdrait la position de défilement et, plus grave, les
   cellules en cours d'édition dont le `blur` n'a pas encore eu lieu.
   ============================================================ */
.onglets {
  display: flex; gap: 4px; flex-wrap: wrap;
  border-bottom: 2px solid var(--bord); margin: 14px 0 0;
}
.onglet {
  background: transparent; color: var(--texte-2); border: 1px solid transparent;
  border-bottom: none; border-radius: var(--r-m) var(--r-m) 0 0;
  padding: 9px 16px; font-size: var(--txt-l); font-weight: 600; width: auto; margin: 0 0 -2px;
  cursor: pointer;
}
.onglet:hover { background: var(--bord); color: var(--texte); }
.onglet-actif, .onglet-actif:hover {
  background: var(--surface); color: var(--action);
  border-color: var(--bord); border-bottom: 2px solid var(--surface);
}
.panneau { padding-top: 14px; }
.panneau[hidden] { display: none; }

/* Barre de filtres — étape 3. Sur 150 modes de défaillance, on ne lit pas le
   tableau : on y cherche quelque chose. */
.filtres {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  padding: 10px 12px; margin: 12px 0 6px;
  background: var(--fond); border: 1px solid var(--bord); border-radius: var(--r-l);
}
.filtre { display: flex; flex-direction: column; }
.filtre label { margin: 0 0 3px; font-size: var(--txt-s); color: var(--texte-2); }
.filtre input, .filtre select { width: auto; min-width: 170px; margin: 0; padding: 7px 9px; font-size: var(--txt-l); }
.filtre-large input { min-width: 260px; }
.filtre-compte {
  font-size: var(--txt-m); font-weight: 600; color: var(--texte-2);
  padding: 8px 10px; background: var(--surface); border: 1px solid var(--bord); border-radius: var(--r-s);
}
.ligne-filtree { display: none; }

/* ============================================================
   SEUILS DE CRITICITÉ
   ------------------------------------------------------------
   ⚠️ AUCUNE BORNE CHIFFRÉE ICI. Les classes portent le NIVEAU, jamais la valeur
   qui le déclenche : le barème a déjà changé une fois (12 → 18 le 2026-08-26).
   Une règle `.criticite-sup-12` serait devenue un mensonge silencieux.
   ============================================================ */
.seuil-Prioritaire { background: var(--erreur-fond); }
.seuil-Prioritaire td.cel-criticite { color: var(--erreur); font-weight: 700; }
.seuil-Renforcé { background: var(--alerte-fond); }
.seuil-Renforcé td.cel-criticite { color: var(--alerte-texte); font-weight: 700; }
.seuil-Standard td.cel-criticite { color: var(--succes-texte); font-weight: 700; }
/* Pas encore classé par le serveur : ligne neuve, ou cote qu'on vient de
   changer. Neutre volontairement — ni rassurant, ni alarmant. */
.seuil-inconnu td.cel-criticite { color: var(--texte-2); font-weight: 700; }
/* « À vérifier » — un LISERÉ et un badge, pas un fond : le fond porte déjà
   le seuil, et le remplacer ferait perdre la criticité de vue. */
tr.ligne-a-verifier > td:first-child { box-shadow: inset 5px 0 0 var(--verifier); }
tr.ligne-a-verifier > td.col-id { background: var(--verifier-fond); }
.badge-a-verifier {
  display: block; width: auto; margin: 4px 0 0; padding: 2px 6px;
  border: 1px solid var(--verifier); border-radius: var(--r-s);
  background: var(--verifier-fond); color: var(--verifier-texte);
  font-size: var(--txt-s); font-weight: 700; line-height: 1.3;
  white-space: nowrap; cursor: pointer;
}
.badge-a-verifier:hover { background: var(--verifier); color: var(--sur-couleur); }
/* Composant sans mode : rien n'est coté, la ligne s'efface d'un cran. */
tr.ligne-sans-mode td { color: var(--texte-3); font-style: italic; }
.decompo-table tbody tr.seuil-Prioritaire:hover { background: var(--erreur-fond); }
.decompo-table tbody tr.seuil-Renforcé:hover { background: var(--alerte-fond); }

/* Répartition par conséquence — deux ramps distincts de ceux des seuils, à
   dessein : ce sont deux lectures différentes de la même analyse, et les
   confondre visuellement ferait croire qu'elles disent la même chose. */
.conseq-0 { fill: var(--erreur); }
.conseq-1 { fill: var(--action); }
.conseq-2 { fill: var(--texte-2); }
.conseq-vide { fill: var(--bord-2); }

/* Intitulé de regroupement par défaillance fonctionnelle. Plusieurs modes
   partagent la même : c'est un affichage, pas un niveau de données. */
.ligne-groupe td {
  background: var(--surlignage); color: var(--action); font-weight: 600;
  padding: 7px 10px; border-top: 2px solid var(--surlignage);
}

/* Une valeur imposée par une règle serveur : visible, mais pas modifiable. */
.valeur-verrouillee {
  display: inline-block; padding: 2px 7px; border-radius: var(--r-s);
  background: var(--erreur-fond); color: var(--erreur); font-size: var(--txt-m); font-weight: 600;
  border: 1px dashed var(--erreur); cursor: help;
}
.texte-alerte { color: var(--alerte-texte); }

/* Une fenêtre d'arrêt qui déborde — le chiffre qu'on emporte en réunion de
   production. Marquée, mais pas criarde : c'est un constat de volume, pas une
   erreur de l'utilisateur. */
.fenetre-debordee {
  border-left: 3px solid var(--alerte-texte);
  padding: .35rem .7rem;
  margin: .5rem 0;
}

/* ------------------------------------------------------------
   L'ARBRE DE DÉCISION RCM (étape 3)
   ------------------------------------------------------------
   Le type n'est plus une saisie : c'est une conclusion. Il doit donc RESSEMBLER
   à une conclusion — pas à un champ qu'on aurait oublié de rendre modifiable. */
.valeur-arbre {
  display: inline-block; padding: 2px 7px; border-radius: var(--r-s);
  background: var(--surlignage); color: var(--action); font-size: var(--txt-m); font-weight: 600;
  border: 1px solid var(--surlignage); cursor: help;
}
.note-arbre { margin-top: 3px; font-size: var(--txt-s); line-height: 1.3; cursor: help; }
.arbre-applique { color: var(--action); }
/* ⚠️ La proposition est ORANGE, pas rouge : ce n'est pas une erreur, c'est une
   décision qui attend quelqu'un. Le rouge est réservé à ce qui est fautif. */
.arbre-propose { color: var(--alerte-texte); font-weight: 600; }
.arbre-indetermine { color: var(--texte-2); }
.arbre-a-rearbitrer { color: var(--texte-2); font-style: italic; }

.btn-arbre {
  margin-top: 4px; padding: 2px 8px; font-size: var(--txt-s); font-weight: 600;
  border: 1px solid var(--alerte); border-radius: var(--r-s);
  background: var(--alerte-fond); color: var(--alerte-texte); cursor: pointer;
}
.btn-arbre:hover:not(:disabled) { background: var(--alerte-fond); }
/* Grisé tant que la justification est vide. L'état désactivé doit se voir
   franchement : à moitié grisé, on clique et on croit que le bouton est cassé. */
.btn-arbre:disabled {
  border-color: var(--bord); background: var(--fond); color: var(--texte-3); cursor: not-allowed;
}

/* Les cinq colonnes dont l'arbre tire le type. C'est ici qu'on corrige quand la
   décision déplaît — le liseré le dit sans qu'il faille l'expliquer. */
.cel-entree-arbre { box-shadow: inset 2px 0 0 var(--surlignage); }
.col-consequence { width: 13%; }
.col-etroite-texte { width: 90px; }

.cel-cote { text-align: center; }
.cote-select {
  width: 46px; padding: 3px 2px; font-size: var(--txt-l); text-align: center;
  border: 1px solid var(--bord); border-radius: var(--r-s); background: var(--surface); margin: 0;
}
.cel-criticite { text-align: center; font-variant-numeric: tabular-nums; }
.col-etroite { width: 46px; }
.col-criticite { width: 62px; }
.col-seuil { width: 108px; }

/* Cartes de synthèse : les nombres qu'on annonce avant de lire le tableau. */
.cartes { display: flex; flex-wrap: wrap; gap: 12px; margin: 4px 0 16px; }
.carte {
  flex: 1 1 165px; background: var(--surface); border: 1px solid var(--bord);
  border-radius: var(--r-l); padding: 12px 14px;
}
.carte-valeur { font-size: var(--txt-chiffre); font-weight: 700; color: var(--action); line-height: 1.15; }
.carte-libelle { font-size: var(--txt-m); color: var(--texte-2); margin-top: 3px; }
.carte-detail { font-size: var(--txt-s); color: var(--texte-3); margin-top: 5px; }
/* Une carte qui pose un filtre se voit : curseur, et un cadre au survol. */
.carte-cliquable { cursor: pointer; }
.carte-cliquable:hover { box-shadow: var(--anneau); }
.carte-cliquable .carte-detail { color: var(--action); text-decoration: underline; }
.carte-Prioritaire .carte-valeur { color: var(--erreur); }
.carte-Renforcé .carte-valeur { color: var(--alerte-texte); }
.carte-Standard .carte-valeur { color: var(--succes); }

/* Répartition par seuil — une barre empilée vaut mieux qu'un camembert :
   on compare des parts d'un même tout, et on les lit alignées.
   ⚠️ En SVG et non en <div> : la CSP interdit les styles en ligne, donc pas de
   largeur en pourcentage posée depuis le JavaScript. Un attribut SVG passe. */
.repartition-svg { display: block; width: 100%; max-width: 900px; height: auto; margin: 4px 0 10px; }
.part-etiquette { fill: var(--surface); font-size: var(--txt-m); font-weight: 700; text-anchor: middle; }
.part-Prioritaire { fill: var(--erreur); }
.part-Renforcé { fill: var(--alerte-texte); }
.part-Standard { fill: var(--succes); }

/* Grille de cotation — les échelles G, P, D. Elles viennent du SERVEUR :
   cf. rcm.COTATION, source unique partagée avec le prompt. */
.cotation { display: flex; flex-wrap: wrap; gap: 16px; }
.cotation-bloc { flex: 1 1 280px; background: var(--surface); border: 1px solid var(--bord); border-radius: var(--r-l); padding: 14px 16px; }
.cotation-bloc h3 { margin: 0 0 10px; font-size: var(--txt-2xl); }
.cotation-niveau { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-top: 1px solid var(--bord); }
.cotation-niveau:first-of-type { border-top: none; }
.cotation-score {
  flex: none; width: 26px; height: 26px; border-radius: var(--r-s);
  background: var(--surlignage); color: var(--action); font-weight: 700; font-size: var(--txt-l);
  display: flex; align-items: center; justify-content: center;
}
.cotation-nom { font-size: var(--txt-l); }

/* ============================================================
   ÉTAPE 4 — profil des pannes en bulles
   ------------------------------------------------------------
   Position = fréquence × durée moyenne, SURFACE = coût annuel. C'est le visuel
   qui montre où est l'argent : un sous-ensemble peu fréquent mais long coûte
   parfois plus qu'un incident quotidien de dix minutes, et un tableau trié ne
   le fait pas voir.
   ⚠️ La surface, pas le rayon : doubler le rayon quadruple la tache perçue et
   exagérerait le coût d'un facteur quatre.
   ============================================================ */
/* ⚠️ `max-width` SUIT LA LARGEUR DU `viewBox` (1180 dans etape4.js). Le laisser
   à l'ancienne valeur bridait le graphique agrandi à sa taille d'avant, sans
   rien casser d'apparent : le dessin était juste rétréci, donc les étiquettes
   des bulles redevenaient illisibles — le défaut qu'on venait de corriger. */
.bulles { display: block; width: 100%; max-width: 1180px; height: auto; margin: 8px 0; }
/* ⚠️ NI `fill` NI `stroke` ICI, et c'est délibéré. Ils y étaient, en indigo
   fixe : toutes les bulles sortaient donc de la même couleur, et sur vingt-cinq
   sous-ensembles on ne les distinguait plus que par leur position. La couleur
   vient maintenant d'une palette indexée posée en ATTRIBUT SVG par le script —
   or une déclaration CSS l'emporterait sur un attribut de présentation, et la
   remettre ici annulerait la palette sans le moindre message.
   L'opacité reste : les bulles se chevauchent là où c'est le plus intéressant,
   en haut à droite du graphique. */
.bulle { fill-opacity: .55; stroke-width: 1.5; }
/* La pastille de rappel entre le tableau et le graphique. */
.col-pastille { width: 26px; }
.pastille-bulle { display: inline-block; line-height: 0; }
.pastille-svg { width: 11px; height: 11px; vertical-align: middle; }
.bulle-etiquette { fill: var(--texte); font-size: var(--txt-s); text-anchor: middle; }
.bulle-axe { stroke: var(--bord-2); stroke-width: 1; }
.bulle-axe-titre { fill: var(--texte-2); font-size: var(--txt-m); }
.bulle-graduation { fill: var(--texte-3); font-size: var(--txt-xs); }

.legende { font-size: var(--txt-m); color: var(--texte-2); margin: 4px 0 14px; }
.mono { font-variant-numeric: tabular-nums; text-align: right; }

/* Le coût horaire d'arrêt, dans la barre d'actions : il multiplie TOUS les
   montants de l'écran, il mérite d'être visible en permanence et pas caché
   dans une modale comme dans le POC. */
.champ-inline { display: inline; margin: 0; font-size: var(--txt-l); }
.champ-taux { width: 92px; margin: 0; padding: 6px 8px; font-size: var(--txt-l); text-align: right; }

/* Signalements portés par le serveur sur une ligne de panne. Ils cohabitent :
   une même panne peut être corrigée d'office ET soupçonnée d'être un doublon. */
.col-signal { width: 52px; white-space: nowrap; }
.duree-corrigee { color: var(--alerte-texte); font-weight: 600; }
.signal-doute { color: var(--alerte-texte); font-weight: 700; cursor: help; }
.signal-doublon { color: var(--texte-2); cursor: help; }
.ligne-recurrente { background: var(--alerte-fond); }
.decompo-table tbody tr.ligne-recurrente:hover { background: var(--alerte-fond); }

/* Les 80 % du Pareto : au-delà, on est dans la queue de la distribution. */
.cumul-vital { font-weight: 700; color: var(--erreur); }

.lien-etape { color: var(--alerte-texte); font-weight: 700; text-decoration: underline; }

/* ============================================================
   ÉTAPE 5 — le fil des jalons
   ------------------------------------------------------------
   Le parcours guidé remplace les douze contraintes d'ordre que le POC faisait
   découvrir par des messages de refus successifs. Le fil montre le chemin
   ENTIER, y compris les jalons pas encore atteints : c'est une carte, pas une
   barre de chargement.
   ============================================================ */
.fil-jalons {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin: 12px 0 8px; padding: 10px 12px;
  background: var(--fond); border: 1px solid var(--bord); border-radius: var(--r-m);
}
.jalon {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: var(--r-pilule);
  font-size: var(--txt-l); white-space: nowrap; cursor: help;
  border: 1px solid transparent;
}
.jalon-puce { font-weight: 700; }
.jalon-libelle { font-weight: 600; }

/* Les six états. Ils ne se distinguent PAS par la seule couleur : chacun porte
   aussi une puce différente — un daltonien doit pouvoir lire le fil, et une
   capture d'écran en noir et blanc reste compréhensible. */
.jalon-bloque     { background: var(--bord); color: var(--texte-3); border-color: var(--bord); }
.jalon-a-faire    { background: var(--surface); color: var(--texte); border-color: var(--bord-2); font-weight: 700; }
.jalon-en-cours   { background: var(--surlignage); color: var(--action); border-color: var(--action); }
.jalon-fait       { background: var(--succes-fond); color: var(--succes-texte); border-color: var(--succes); }
.jalon-passe      { background: var(--alerte-fond); color: var(--alerte-texte); border-color: var(--alerte); }
.jalon-sans-objet { background: var(--fond); color: var(--texte-2); border-color: var(--bord); }

/* Le jalon où l'on en est : il porte l'action, et lui seul. */
.encart-jalon {
  margin: 8px 0 14px; padding: 12px 14px;
  background: var(--surlignage); border-left: 4px solid var(--action); border-radius: var(--r-xs);
}
.actions-jalon { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

/* Les jalons franchis sans avoir été faits — sans objet, ou passés sciemment.
   ⚠️ DISCRET MAIS PAS CACHÉ, ET SURTOUT PAS DANS UNE INFOBULLE : c'est ce qu'on
   relit des mois plus tard pour justifier au client qu'une étape du parcours n'a
   pas eu lieu. Une information qui n'existe qu'au survol n'existe pas à
   l'impression. */
.jalons-traverses { margin: 0 0 12px; font-size: var(--txt-l); }
.jalons-traverses ul { margin: 4px 0 0 18px; color: var(--texte-2); }
.jalons-traverses li { margin-bottom: 3px; }

/* ⚠️ EXCLUE N'EST PAS MASQUÉE. Une tâche exclue des modes opératoires reste au
   plan et reste LISIBLE : c'est précisément celle qu'on veut pouvoir relire
   avant de valider. `.ligne-filtree` (display:none) est réservée aux filtres. */
.ligne-exclue { opacity: 0.55; }
.ligne-exclue td { text-decoration: line-through; }
.ligne-exclue td:last-child { text-decoration: none; }

/* ------------------------------------------------------------
   Une valeur qui vient d'une AUTRE étape (étape 4 : le coût d'arrêt).
   ------------------------------------------------------------
   Elle se lit, elle ne se saisit pas. La distinguer d'un champ est ce qui
   évite qu'on cherche à la corriger là où elle est seulement affichée. */
.valeur-heritee {
  display: inline-block; padding: 2px 8px; border-radius: var(--r-s);
  background: var(--bord); color: var(--texte); font-size: var(--txt-l); font-weight: 600;
  border: 1px solid var(--bord); cursor: help;
}
/* ⚠️ Un coût ESTIMÉ a la même valeur qu'un coût saisi et pas le même poids :
   le premier n'engage personne, et c'est pourtant lui qui produirait le montant
   annuel d'une revue de fiabilité. La différence doit se voir. */
.valeur-estimee { background: var(--alerte-fond); color: var(--alerte-texte); border-color: var(--alerte); }

/* ------------------------------------------------------------
   Confrontation au plan existant (étape 5)
   ------------------------------------------------------------
   ⚠️ Écarter une activité que le CLIENT APPLIQUE demande une justification
   écrite. La cellule le dit AVANT l'envoi : découvrir l'exigence par un refus
   du serveur fait écrire n'importe quoi pour passer. */
.justification-attendue {
  background: var(--alerte-fond);
  box-shadow: inset 0 0 0 2px var(--alerte);
}
.justification-attendue:empty::before {
  content: attr(data-aide);
  color: var(--alerte-texte); font-size: var(--txt-m); font-style: italic;
}

/* « Cachée ? » — un menu Oui/Non, pas un chiffre. Elle partageait la largeur des
   cotes G/P/D (46 px) et sortait tronquée à « N… ». Une entrée de l'arbre de
   décision qu'on ne peut pas lire est une entrée qu'on ne corrige pas. */
.col-cachee { min-width: 86px; }

/* ------------------------------------------------------------
   Répartition par seuil — le camembert (étape 3)
   ------------------------------------------------------------
   Trois nombres côte à côte se lisent un par un ; ce qu'on veut voir d'un coup
   d'œil, c'est la PROPORTION. Si le prioritaire mange la moitié de l'analyse,
   elle ne hiérarchise plus rien — et c'est la forme qui le dit avant le chiffre.
   Les couleurs sont celles de `.part-<seuil>`, partagées avec la barre des
   conséquences : deux lectures de la même analyse, un seul jeu de couleurs. */
.camembert-bloc {
  display: flex; align-items: center; gap: 28px; flex-wrap: wrap;
  margin: 4px 0 18px;
}
.camembert { width: 230px; height: 230px; flex: 0 0 auto; }
.camembert-legende { display: flex; flex-direction: column; gap: 8px; font-size: var(--txt-xl); }
.camembert-ligne { display: flex; align-items: center; gap: 8px; }
.camembert-pastille { width: 12px; height: 12px; flex: 0 0 auto; }
/* La légende sous la barre des conséquences : en ligne, et qui passe à la
   ligne sur un écran étroit. Mêmes pastilles que celle du camembert. */
.repartition-legende {
  display: flex; flex-wrap: wrap; gap: 6px 22px; margin: -4px 0 14px; font-size: var(--txt-l);
}
.camembert-valeur { font-weight: 700; font-size: var(--txt-titre); min-width: 34px; text-align: right; }
/* L'étiquette est posée SUR la tranche : elle doit tenir quel que soit le fond. */
.camembert .part-etiquette { fill: var(--surface); font-size: var(--txt-l); font-weight: 700; text-anchor: middle; }

/* ------------------------------------------------------------
   Périphériques de l'étape 1
   ------------------------------------------------------------
   Ils se cochent en faisant le tour de la machine : les groupes suivent ce
   parcours, pas un ordre alphabétique. Non coché = aucune tâche au plan, et
   c'est le genre d'oubli qu'on ne découvre qu'en réunion client. */
#peripheriques {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 14px; margin: 10px 0 18px;
}
.famille-peripherique {
  border: 1px solid var(--bord); border-radius: var(--r-l); padding: 10px 12px; background: var(--surface);
}
.famille-titre { font-weight: 600; font-size: var(--txt-xl); margin-bottom: 6px; }
.peripherique {
  display: flex; align-items: flex-start; gap: 7px;
  margin: 0; padding: 3px 0; font-weight: 400; font-size: var(--txt-l); cursor: pointer;
}
.peripherique input { width: auto; margin: 2px 0 0; flex: 0 0 auto; }
/* ⚖️ marque les périphériques porteurs d'une OBLIGATION réglementaire. Ce n'est
   pas un conseil : c'est ce qu'un inspecteur vient vérifier. */
.pastille-regl { flex: 0 0 auto; font-size: var(--txt-m); opacity: .75; }

/* ============================================================
   ÉTAPE 6 — les gammes, et le relevé des sources
   ============================================================ */

/* Le relevé des sources : il est partagé avec l'étape 5 dans l'esprit, mais ses
   classes n'existaient pas — l'étape 5 le dessine dans son propre bloc. */
.releve-sources { margin: 10px 0 14px; font-size: var(--txt-l); }
.releve-sources ul { margin: 4px 0 0; padding-left: 20px; }
.releve-sources li { margin: 3px 0; }
/* ⚠️ UNE SOURCE ABSENTE SE VOIT. Un expert devine qu'une source a manqué ; un
   technicien croit que le document est complet. */
.source-absente { color: var(--alerte-texte); }

/* ------------------------------------------------------------
   UNE GAMME = UNE INTERVENTION, repliée par défaut.
   `<details>` natif : il se déplie au clavier, il s'imprime, et il ne demande
   aucun état à tenir côté navigateur — donc rien à resynchroniser au redessin.
   ------------------------------------------------------------ */
.gamme {
  border: 1px solid var(--bord); border-radius: var(--r-m); margin: 8px 0;
  background: var(--surface); overflow: hidden;
}
.gamme[open] { border-color: var(--surlignage); box-shadow: var(--ombre-1); }

.gamme-tete {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; cursor: pointer; list-style: none;
}
.gamme-tete::-webkit-details-marker { display: none; }
.gamme-tete::before { content: '▸'; color: var(--texte-3); flex: 0 0 auto; }
.gamme[open] .gamme-tete::before { content: '▾'; }
.gamme-tete:hover { background: var(--fond); }

.gamme-id { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: var(--txt-m); color: var(--texte-2); }
.gamme-titre { font-weight: 600; flex: 1 1 240px; }
.gamme-chiffres { display: flex; gap: 6px; flex-wrap: wrap; }
.gamme-tag {
  font-size: var(--txt-s); padding: 2px 7px; border-radius: var(--r-l);
  background: var(--bord); color: var(--texte-2); white-space: nowrap;
}
/* 🔴 LA CONSIGNATION SE VOIT SANS DÉPLIER. C'est une règle de sécurité. */
.gamme-tag-loto { background: var(--erreur-fond); color: var(--erreur); font-weight: 600; }
/* Une gamme née du garde-fou de couverture dit CE QU'IL FAUT FAIRE, pas
   COMMENT : elle part en atelier incomplète si personne ne la voit. */
.gamme-tag-auto { background: var(--alerte-fond); color: var(--alerte-texte); font-weight: 600; }

.gamme-corps { padding: 4px 14px 14px; border-top: 1px solid var(--bord); }
.gamme-entete { display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 10px 0 14px; font-size: var(--txt-l); }
/* ============================================================
   L'APERÇU DU MODE OPÉRATOIRE — étape 6
   ------------------------------------------------------------
   🔴 CE QUI EST DESSINÉ ICI EST CE QUI PART EN ATELIER. La feuille se tient à
   la main devant une machine à l'arrêt, par quelqu'un qui n'a assisté à aucune
   des réunions. Elle doit donc se lire de loin, sans couleur pour l'essentiel —
   une gamme se photocopie en noir et blanc — et se hiérarchiser par la taille
   et le trait plutôt que par la teinte.
   ⚠️ Aucun style en ligne : la CSP du socle les refuse, et un style posé au fil
   du code finit par se disputer avec cette feuille sans que rien ne le signale.
   ============================================================ */
.sop-volets { display: flex; gap: 14px; align-items: flex-start; }
.sop-rail {
  flex: 0 0 320px; max-height: calc(100vh - 300px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 6px;
}
.sop-scene { flex: 1 1 auto; min-width: 0; }

.sop-rail-item {
  display: flex; flex-direction: column; gap: 4px; align-items: flex-start;
  width: 100%; text-align: left; padding: 8px 10px; margin: 0;
  background: var(--surface); border: 1px solid var(--bord); border-radius: var(--r-m);
  cursor: pointer; font-size: var(--txt-l); color: inherit;
}
.sop-rail-item:hover { border-color: var(--surlignage); background: var(--fond); }
.sop-rail-actif {
  border-color: var(--action); background: var(--surlignage);
  box-shadow: inset 3px 0 0 var(--action);
}
.sop-rail-titre { font-weight: 600; line-height: 1.3; }
.sop-rail-tags { display: flex; flex-wrap: wrap; gap: 4px; }

.sop-doc {
  background: var(--surface); border: 1px solid var(--bord); border-radius: var(--r-l);
  overflow: hidden; font-size: var(--txt-l);
}
.sop-doc-tete {
  display: flex; align-items: center; gap: 16px;
  padding: 12px 18px; border-bottom: 2px solid var(--action);
}
.sop-logo {
  background: var(--action); color: var(--surface); font-weight: 700; letter-spacing: .02em;
  padding: 8px 14px; border-radius: var(--r-m);
}
.sop-tete-titre { flex: 1 1 auto; text-align: center; min-width: 0; }
.sop-tete-titre h2 {
  margin: 0; font-size: var(--txt-2xl); text-transform: uppercase; letter-spacing: .04em;
}
.sop-sous-titre { font-size: var(--txt-l); color: var(--texte-2); margin-top: 2px; }
.sop-tete-rev { text-align: right; font-size: var(--txt-m); color: var(--texte-2); white-space: nowrap; }
.sop-rev-valeur { font-weight: 700; color: var(--texte); }

/* Quatre cases par ligne : c'est la grille d'un formulaire d'atelier, et elle
   se replie à deux sur un écran étroit plutôt que de comprimer les libellés. */
.sop-grille {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-bottom: 1px solid var(--bord);
}
@media (max-width: 1100px) { .sop-grille { grid-template-columns: repeat(2, 1fr); } }
.sop-case { padding: 8px 14px; border-right: 1px solid var(--bord); }
.sop-case-label {
  font-size: var(--txt-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--texte-2);
}
.sop-case-valeur { font-weight: 600; margin-top: 2px; }
.sop-niveau-n1 { color: var(--action); }
.sop-niveau-n2 { color: var(--alerte-texte); }
.sop-niveau-n3 { color: var(--action); }

.sop-bloc { padding: 8px 14px; border-bottom: 1px solid var(--bord); }
.sop-epi { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0; }
.sop-epi-badge {
  background: var(--surlignage); border: 1px solid var(--surlignage); color: var(--action);
  border-radius: var(--r-pilule); padding: 2px 10px; font-size: var(--txt-m);
}
.sop-epi-manque { color: var(--erreur); font-size: var(--txt-m); }
.sop-epi-tete { display: flex; align-items: center; gap: 10px; }
.sop-epi-saisie { display: block; margin-top: 4px; }
/* Un bouton qui ne se donne pas l'air d'une action du document : il ouvre une
   saisie, il ne change rien par lui-même. */
.sop-modifier {
  background: none; border: none; box-shadow: none; width: auto; margin: 0;
  padding: 0 4px; font-size: var(--txt-s); color: var(--texte-2); text-decoration: underline;
}
.sop-modifier:hover { color: var(--action); }

.sop-consignes {
  padding: 10px 14px; background: var(--alerte-fond); border-bottom: 1px solid var(--alerte-fond);
  line-height: 1.5;
}
.sop-section {
  padding: 8px 14px 4px; font-weight: 700; font-size: var(--txt-m);
  text-transform: uppercase; letter-spacing: .03em; color: var(--texte);
}

.sop-table { width: 100%; border-collapse: collapse; font-size: var(--txt-m); }
.sop-table th {
  text-align: left; font-weight: 600; font-size: var(--txt-xs); text-transform: uppercase;
  letter-spacing: .03em; color: var(--texte-2); padding: 6px 10px;
  border-bottom: 1px solid var(--bord); background: var(--fond);
}
.sop-table td {
  padding: 6px 10px; border-bottom: 1px solid var(--bord); vertical-align: top;
}

/* 🔴 LE BANDEAU DE CONSIGNATION SE VOIT DE LOIN, et il coupe la séquence en
   deux : ce qui est au-dessus se fait machine en marche. C'est la seule ligne
   du document dont l'omission peut blesser quelqu'un. */
.sop-ligne-loto td {
  background: var(--erreur-fond); color: var(--erreur); font-weight: 700;
  border-top: 2px solid var(--erreur); border-bottom: 2px solid var(--erreur);
  letter-spacing: .02em;
}
.op-etat { white-space: nowrap; color: var(--texte-2); }
.op-etat-loto { color: var(--erreur); font-weight: 600; }

/* ------------------------------------------------------------
   LES ILLUSTRATIONS D'UNE OPÉRATION
   🔴 LES PAIRES OK / NOK SE LISENT CÔTE À CÔTE — c'est le standard visuel de la
   TPM, et ce n'est pas de la mise en page : c'est la paire qui apprend, pas la
   photo isolée. D'où la bande horizontale plutôt qu'une image par ligne.
   ------------------------------------------------------------ */
.ligne-illus td { background: var(--surface); padding: 6px 10px 10px; }
.sop-illus { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; }
.sop-illus-vignette {
  margin: 0; width: 170px; border: 1px solid var(--bord); border-radius: var(--r-m);
  overflow: hidden; background: var(--surface);
}
.sop-illus-vignette img { display: block; width: 100%; height: 110px; object-fit: cover; }
.sop-illus-bas {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 4px 6px; font-size: var(--txt-s); border-top: 1px solid var(--bord);
}
.sop-illus-bas .select-nature { width: auto; font-size: var(--txt-s); padding: 1px 4px; }
.sop-illus-bas .gamme-champ-valeur { min-width: 60px; font-size: var(--txt-s); }
.sop-illus-retirer {
  background: none; border: none; box-shadow: none; width: auto; margin: 0;
  padding: 0 4px; color: var(--texte-3); font-size: var(--txt-m);
}
.sop-illus-retirer:hover { color: var(--erreur); }
/* Un lien rompu se voit À SA PLACE, pas seulement dans un compteur : la gamme
   promet une image, et c'est là qu'elle manque. */
.sop-illus-rompu {
  height: 110px; display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 8px; font-size: var(--txt-s);
  background: var(--erreur-fond); color: var(--erreur);
}
/* La cible de dépôt reste offerte même quand des images sont déjà là : une
   opération en porte souvent deux — l'état correct et le défaut. */
.sop-illus-depot {
  width: 170px; height: 148px; display: flex; align-items: center;
  justify-content: center; text-align: center; padding: 8px; cursor: pointer;
  border: 1px dashed var(--bord-2); border-radius: var(--r-m); color: var(--texte-2); font-size: var(--txt-s);
}
.sop-illus-depot:hover, .sop-illus-survol { border-color: var(--action); background: var(--fond); }
.sop-illus-depot input { display: none; }

.sop-cloture { padding: 8px 14px; background: var(--succes-fond); border-top: 1px solid var(--succes-fond); }
.sop-cloture ul { margin: 4px 0 0 18px; padding: 0; }
.sop-cloture-generique { background: var(--alerte-fond); border-top-color: var(--alerte-fond); }

.sop-doc-pied {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 8px 14px; border-top: 1px solid var(--bord);
  font-size: var(--txt-s); color: var(--texte-2);
}

.gamme-champ { flex: 0 1 auto; }
.gamme-champ-libelle { color: var(--texte-2); margin-right: 6px; }

/* 🔴 UNE CASE VIDE DOIT RESTER CLIQUABLE. Un `contenteditable` sans contenu a
   une largeur nulle : le champ « EPI » d'une gamme qui n'en a pas — exactement
   celui qu'on vient remplir — n'offrirait rien à viser. Le trait pointillé dit
   « ici on écrit » avant même le survol. */
.gamme-champ-valeur {
  display: inline-block; min-width: 90px; padding: 1px 4px;
  border-bottom: 1px dashed var(--bord-2);
}
.gamme-champ-valeur:focus { border-bottom-color: transparent; }

.tableau-gamme { width: 100%; border-collapse: collapse; font-size: var(--txt-l); }
.tableau-gamme th {
  text-align: left; font-weight: 600; font-size: var(--txt-s); text-transform: uppercase;
  letter-spacing: .03em; color: var(--texte-2); padding: 6px 8px; border-bottom: 1px solid var(--bord);
}
.tableau-gamme td { padding: 7px 8px; border-bottom: 1px solid var(--bord); vertical-align: top; }
.op-num { color: var(--texte-3); width: 28px; }
.op-temps { white-space: nowrap; text-align: right; width: 70px; }

/* 🔴 L'IDENTIFIANT DE TÂCHE EST VISIBLE, et son absence encore plus. C'est par
   lui que la gamme est reliée au plan ; une opération sans lui ne couvre RIEN. */
.op-tache {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: var(--txt-s); color: var(--texte-2);
}
.op-tache-absente { font-size: var(--txt-s); color: var(--erreur); font-weight: 600; }

.ligne-auto td { background: var(--alerte-fond); }
.ligne-refdoc td { background: var(--fond); color: var(--texte-2); font-size: var(--txt-m); padding-top: 2px; }

/* Le pendant NEUTRE de `.zone-destructive` : même place, même bouton, mais rien
   à perdre. Sert à la première génération d'une étape, avant que le geste ne
   devienne un remplacement. */
.zone-action { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 2rem 0 1rem; padding: 14px 16px;
  background: var(--fond); border: 1px solid var(--bord); border-radius: var(--r-m); }

/* ------------------------------------------------------------
   Le geste qui fait entrer les décisions au plan (étape 5)
   🔴 Il était en gris, petit, en bas d'un tableau de plusieurs écrans — au même
   poids visuel qu'un bouton secondaire, alors qu'il est l'aboutissement de tout
   l'arbitrage. Relevé par Pascal le 2026-09-02.
   ------------------------------------------------------------ */
.btn-large { font-size: var(--txt-2xl); padding: 10px 20px; font-weight: 600; }
/* 🔴 SANS CETTE LIGNE, `.btn-large` NE FAIT RIEN LÀ OÙ ELLE SERT (2026-09-07).
   Son unique usage — « Appliquer les décisions » — est dans une barre
   d'actions, et `.barre-actions button` (spécificité 0,1,1) l'emporte sur
   `.btn-large` (0,1,0) : le bouton qu'on voulait plus gros avait exactement la
   taille des autres. Mesuré : 7px 13px et 13px de police au lieu de 10px 20px
   et 15px. Le commentaire d'`etape5.js:948` dit pourtant pourquoi il doit se
   distinguer — « un bouton secondaire le faisait chercher ». */
.barre-actions .btn-large { padding: 10px 20px; font-size: var(--txt-2xl); }
.barre-appliquer { align-items: center; gap: 16px; margin-top: 14px; }

/* Le retour, À CÔTÉ du bouton. Le tableau fait plusieurs écrans : un message
   qui ne paraît que dans la bannière du haut n'est pas lu par qui vient de
   cliquer en bas — il en conclut qu'il ne s'est rien passé. */
.retour-appliquer { font-size: var(--txt-xl); color: var(--succes-texte); font-weight: 500; }
.retour-appliquer.retour-visible {
  background: var(--succes-fond); border: 1px solid var(--succes); border-radius: var(--r-s); padding: 6px 12px;
}

/* ============================================================
   ÉTAPE 5 — LA VUE COMPLÈTE : le tableau du POC (2026-09-02)
   ============================================================ */

/* 🔴 VINGT COLONNES NE TIENNENT PAS DANS 100 % D'ÉCRAN. En vue complète, on
   rend la main à `table-layout: auto` et on laisse le CADRE défiler.
   La vue Revue, elle, garde ses largeurs fixes totalisant 100 %. */
.table-plan.table-large { table-layout: auto; min-width: 2100px; }

/* 🔴 LE CADRE DEVIENT LA FENÊTRE DU TABLEAU, et c'est ce qui rend les en-têtes
   collants JUSTES. Le défaut d'avant venait d'un cadre qui défilait SANS
   hauteur bornée : les en-têtes s'y rattachaient alors qu'on attendait qu'ils
   se rattachent à la page, et ils réapparaissaient au milieu des lignes.
   Avec une hauteur, le cadre est une fenêtre — s'y coller est le comportement
   voulu, plus un accident. */
.cadre-defilant {
  overflow: auto;
  max-height: calc(100vh - 320px);
  border: 1px solid var(--bord); border-radius: var(--r-m);
}
.cadre-defilant thead th { position: sticky; top: 0; z-index: 3; }
.table-plan.table-large td, .table-plan.table-large th { white-space: normal; }
.table-plan.table-large .c-num { width: 78px; }
.table-plan.table-large .c-pet { width: 110px; }
.table-plan.table-large .c-moy { width: 150px; }
.table-plan.table-large .c-gr  { width: 230px; }
.table-plan.table-large .c-xl  { width: 300px; }

/* Un en-tête triable annonce qu'il l'est, et où il en est. */
.th-triable { cursor: pointer; user-select: none; }
.th-triable:hover { background: var(--surlignage); }
.th-fleche { opacity: .5; font-size: .85em; margin-left: 2px; }

/* Une cellule dérivée ne se saisit pas : elle n'a donc pas l'aspect d'un champ.
   Le contraire mettrait à l'écran une valeur que le prochain enregistrement
   écraserait — le défaut que le PERT du POC portait. */
.cellule-derivee { background: var(--surface); }

/* « Couvre » : une pastille par référentiel, « RCM+RCA » en fait deux. */
.pastille-couvre {
  display: inline-block; font-size: var(--txt-xs); font-weight: 700; padding: 1px 6px;
  border-radius: var(--r-l); margin-right: 3px; white-space: nowrap;
}
.pastille-rcm { background: var(--surlignage); color: var(--action); }
.pastille-rca { background: var(--erreur-fond); color: var(--erreur); }

.pastille-tier {
  display: inline-block; width: 20px; text-align: center; font-weight: 700;
  font-size: var(--txt-s); border-radius: var(--r-xs);
}
.pastille-tier-A { background: var(--erreur-fond); color: var(--erreur); }
.pastille-tier-B { background: var(--alerte-fond); color: var(--alerte-texte); }
.pastille-tier-C { background: var(--succes-fond); color: var(--succes-texte); }

/* Le PERT : un résultat, et le clic qui ouvre ce dont il découle. */
.pert-valeur {
  background: var(--surlignage); border: 1px solid var(--surlignage); border-radius: var(--r-s);
  padding: 2px 8px; font-weight: 600; cursor: pointer; font-size: var(--txt-m);
}
.pert-valeur:hover { background: var(--surlignage); }
.estimations { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
.estimation { display: flex; align-items: center; gap: 6px; font-size: var(--txt-s); }
.estimation input { width: 62px; padding: 2px 4px; font-size: var(--txt-m); }

/* Le regroupement décidé au challenge — appliqué sur la tâche depuis toujours,
   affiché nulle part jusqu'au 2026-09-02. */
.pastille-groupe {
  display: inline-block; font-size: var(--txt-xs); font-weight: 700; padding: 1px 6px;
  border-radius: var(--r-l); background: var(--surlignage); color: var(--action); white-space: nowrap;
}

/* ============================================================
   ÉTAPE 8 — LE CHOIX DE LA GMAO CIBLE (forme du POC, 2026-09-03)
   ============================================================
   🔴 CES SIX CARTES SONT UN ARGUMENT, pas une décoration. Elles disent au
   client, en un regard, que l'outil ne parle pas à UNE GMAO mais à la sienne —
   c'est la raison pour laquelle les quatre connecteurs annoncés restent
   visibles et grisés plutôt que masqués. Relevé par Pascal.
   Les logos sont servis par nous : la CSP interdit toute image externe. */
.gmao-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 12px; margin-bottom: 6px;
}
/* Une carte est un `button` : on y arrive au clavier, et le lecteur d'écran
   annonce un contrôle. D'où les remises à zéro — le bouton du socle est bleu et
   pleine largeur. */
.gmao-card {
  width: auto; margin: 0; padding: 20px 14px; text-align: center; position: relative;
  background: var(--surface); color: var(--texte); border: 2px solid var(--bord-2); border-radius: var(--r-xl);
  font-size: var(--txt-xl); font-weight: 400; cursor: pointer; transition: all .15s;
}
.gmao-card:hover { background: var(--surface); border-color: var(--action); transform: translateY(-2px); }
.gmao-card.gmao-choisi {
  border-color: var(--action); background: var(--surlignage);
  box-shadow: var(--ombre-action);
}
/* Annoncé, pas masqué — et non cliquable, pour que la promesse n'ait pas l'air
   d'un bouton en panne. */
.gmao-card.gmao-annonce { opacity: .45; cursor: not-allowed; }
.gmao-card.gmao-annonce:hover { border-color: var(--bord-2); transform: none; }
/* ⚠️ LE TEXTE VIENT DE L'ATTRIBUT, PAS DE LA FEUILLE (2026-09-07). Il était
   écrit ici, en français, et c'est la QUATRIÈME source de texte visible du
   produit — après le HTML, le JavaScript et le serveur : ni le dictionnaire ni
   aucun contrôle ne la voyaient, si bien que quatre cartes restaient françaises
   sur un écran polonais entièrement traduit. `attr()` est le motif déjà employé
   par `[data-aide]` plus haut : la feuille décide de la FORME, le script fournit
   le MOT. Un `content:` porteur de texte est désormais refusé par check_i18n. */
.gmao-card.gmao-annonce::after {
  content: attr(data-badge); position: absolute; top: 8px; right: 8px;
  font-size: var(--txt-xs); text-transform: uppercase; letter-spacing: .3px;
  background: var(--sur-couleur); color: var(--texte-2); padding: 2px 8px; border-radius: var(--r-l);
}
.gmao-logo {
  min-height: 42px; margin-bottom: 8px;
  display: flex; align-items: center; justify-content: center;
}
.gmao-logo img { max-height: 40px; width: auto; max-width: 80%; object-fit: contain; }
.gmao-nom { font-weight: 700; font-size: var(--txt-xl); }
.gmao-soustitre { font-size: var(--txt-s); color: var(--texte-2); margin-top: 4px; }

/* ------------------------------------------------------------
   Étape 2 — le choix du parcours : analyse complète ou SOP express
   (décision du 2026-09-10). Deux cartes côte à côte, celle retenue marquée.
   ------------------------------------------------------------ */
.parcours-bloc { background: var(--surface); border: 1px solid var(--bord); border-radius: var(--r-m); padding: 12px 16px; margin: 12px 0; }
.parcours-bloc h3 { margin: 0 0 4px; font-size: var(--txt-xl); }
.parcours-bloc p { margin: 4px 0; }
.parcours-choix { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; margin-top: 10px; }
.parcours-carte { border: 1px solid var(--bord); border-radius: var(--r-m); padding: 10px 12px; background: var(--fond); }
.parcours-carte.parcours-retenu { border-color: var(--action); background: var(--info-fond); }
.parcours-carte h4 { margin: 0 0 4px; font-size: var(--txt-l); }
.parcours-carte p { margin: 0 0 8px; font-size: var(--txt-m); }

/* ------------------------------------------------------------
   « À faire maintenant » — la première génération (étapes 2 à 6)
   ------------------------------------------------------------
   Pascal, 2026-09-11 : « le bouton est gris et petit, étant donné que c'est la
   première chose à faire ». Le geste qui ouvre l'écran vit dans l'encadré bleu
   qu'on lit, en bouton LARGE et BLEU — la couleur d'action du produit, pas le
   vert, qui dit « fait ». Posé en fin de feuille : il doit l'emporter sur
   `.btn-primary`, de même spécificité. Voir public/afaire.js. */
.encart-a-faire { margin: 10px 0 16px; }
.btn-a-faire { padding: 11px 24px; font-size: var(--txt-2xl); font-weight: 700;
  box-shadow: var(--ombre-action); }
.btn-a-faire:disabled { box-shadow: none; }
