/*
 * registre.css — la feuille de système de la charte A2 (docs/CHARTE.md).
 * Jetons, composants et retaillage Bootstrap : un écran lit ces jetons, il ne
 * recopie jamais une valeur. Se charge en dernier (index.php) : il redéclare
 * des jetons de app.css et gagne par l'ordre, pas par !important.
 */

/* Fontes auto-hébergées, jamais de CDN : le comptoir s'affiche hors ligne.
   Sous-ensemble latin (œ compris). Chiffres de Plex Sans tous à 600 unités. */
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-v530-latin-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-v530-latin-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-v530-latin-600.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/ibm-plex-sans-v530-latin-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Encres, du plus lu au moins lu. */
  --ink:   #1B1F24;
  --ink-2: #4B5563;
  --ink-3: #7B8592;

  /* Fond de page, panneau, lavis en retrait. */
  --paper: #F4F5F7;
  --panel: #FFFFFF;
  --wash:  #ECEFF2;

  --rule:   #E3E7EC;
  --rule-2: #C9D0D8;

  /* L'accent : l'action et la sélection. */
  --acc:   #1F5FBF;
  --acc-w: #E8F0FC;
  --acc-h: #184C99;

  /* Le rouge : ce qui coûte du temps ou de l'argent (urgent, retard, erreur). */
  --red:   #C0261C;
  --red-w: #FCE9E7;

  /* La marque Honda : logo et compteurs du menu, jamais un état. */
  --marque: #C8102E;

  /* Échelles d'information, nommées par leur sens. Le manque reprend --red. */
  --ecart-plus:    #1E7B4B;
  --ecart-plus-w:  #E3F3E9;

  --etat-neutre:     #8A94A1;
  --etat-neutre-w:   #EEF0F3;
  --etat-ok:         var(--ecart-plus);
  --etat-ok-w:       var(--ecart-plus-w);
  --etat-attention:  #9A6400;
  --etat-attention-w:#FFF6E0;
  --etat-ecart:      var(--red);
  --etat-ecart-w:    var(--red-w);

  --actif:   var(--acc);
  --actif-w: var(--acc-w);

  /* Repères de calendrier : un fond que l'œil trouve et ne lit pas. */
  --repere-ferie-w:      #EEF2F8;
  --repere-aujourdhui-w: #FFF8E1;

  /* Indisponibilité : une hachure, pas une teinte. */
  --trame-indispo: repeating-linear-gradient(
      135deg, transparent 0 6px, var(--rule-2) 6px 7px);

  /* Coins : rendez-vous, bouton ou champ, menu. Pas d'ombre portée. */
  --coin-s: 3px;
  --coin:   5px;
  --coin-l: 8px;

  /* La barre de pied, à hauteur fixe. */
  --h-pied:      28px;

  /* Une seule famille ; --fonte-mono reste pour les écrans qui la nomment. */
  --fonte:      'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --fonte-mono: var(--fonte);
}

/* ── Retaillage Bootstrap : ses variables de composant remontent vers ces
   jetons racine ; les régler ici règle les composants sans !important. */

:root {
  --bs-border-radius: var(--coin);
  --bs-border-radius-sm: 4px;
  --bs-border-radius-lg: 6px;
  --bs-border-radius-xl: var(--coin-l);
  --bs-border-radius-xxl: var(--coin-l);
  --bs-border-radius-pill: 50rem;
  --bs-box-shadow: none;
  --bs-box-shadow-sm: none;
  --bs-box-shadow-lg: none;
  --bs-box-shadow-inset: none;

  --bs-body-bg: var(--paper);
  --bs-body-color: var(--ink);
  --bs-secondary-color: var(--ink-2);
  --bs-tertiary-color: var(--ink-3);
  --bs-border-color: var(--rule);
  --bs-emphasis-color: var(--ink);
  --bs-secondary-bg: var(--wash);
  --bs-tertiary-bg: var(--wash);

  --bs-body-font-family: var(--fonte);
  --bs-font-sans-serif: var(--fonte);
  --bs-font-monospace: var(--fonte);
  --bs-body-font-size: 0.875rem;
  --bs-body-line-height: 1.4;

  --bs-primary: var(--acc);
  --bs-primary-rgb: 31, 95, 191;
  --bs-danger: var(--red);
  --bs-danger-rgb: 192, 38, 28;

  --bs-link-color: var(--acc);
  --bs-link-color-rgb: 31, 95, 191;
  --bs-link-hover-color: var(--acc-h);
  --bs-link-hover-color-rgb: 24, 76, 153;
  --bs-link-decoration: none;
}

body {
  font-family: var(--fonte);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--paper);
}
a:hover { text-decoration: underline; }
/* Un numéro ne se coupe jamais : on le lit et on le compose d'un bloc. */
a[href^="tel:"] { white-space: nowrap; }

/* Le focus clavier se voit toujours (tout au clavier). */
:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
}
.btn { --bs-btn-focus-box-shadow: none; }
/* Un champ est un panneau blanc sous un filet marqué, posé sur le fond gris. */
.form-control,
.form-select {
  background-color: var(--panel);
  border-color: var(--rule-2);
}
.form-control:disabled,
.form-select:disabled { background-color: var(--wash); }
.input-group-text { background-color: var(--wash); border-color: var(--rule-2); }
.form-control:focus,
.form-select:focus {
  border-color: var(--acc);
  outline: 1px solid var(--acc);
  outline-offset: -1px;
  box-shadow: none;
}

/* Boutons : accent plein pour l'action principale, filet pour le reste,
   rouge pour ce qui détruit. Pas de vert, jaune ni cyan Bootstrap. */
.btn-primary {
  --bs-btn-bg: var(--acc);
  --bs-btn-border-color: var(--acc);
  --bs-btn-color: #fff;
  --bs-btn-font-weight: 600;
  --bs-btn-hover-bg: var(--acc-h);
  --bs-btn-hover-border-color: var(--acc-h);
  --bs-btn-active-bg: var(--acc-h);
  --bs-btn-active-border-color: var(--acc-h);
  --bs-btn-disabled-bg: var(--ink-3);
  --bs-btn-disabled-border-color: var(--ink-3);
}
/* Bootstrap déclare la couleur désactivée variante par variante : la régler ici. */
.btn-secondary,
.btn-outline-primary,
.btn-outline-secondary {
  --bs-btn-bg: var(--panel);
  --bs-btn-border-color: var(--rule-2);
  --bs-btn-color: var(--ink);
  --bs-btn-hover-bg: var(--wash);
  --bs-btn-hover-border-color: var(--ink-3);
  --bs-btn-hover-color: var(--ink);
  --bs-btn-active-bg: var(--wash);
  --bs-btn-active-border-color: var(--ink-2);
  --bs-btn-active-color: var(--ink);
  --bs-btn-disabled-color: var(--ink-3);
  --bs-btn-disabled-border-color: var(--rule-2);
}
/* Un choix à positions : la position qui vaut est à l'encre, comme la maquette. */
.btn-check:checked + .btn-outline-secondary,
.btn-check:active + .btn-outline-secondary,
.btn-check:checked + .btn-outline-primary,
.btn-check:active + .btn-outline-primary {
  --bs-btn-active-bg: var(--ink);
  --bs-btn-active-border-color: var(--ink);
  --bs-btn-active-color: #fff;
}

.btn-danger {
  --bs-btn-bg: var(--red);
  --bs-btn-border-color: var(--red);
  --bs-btn-hover-bg: #A11F17;
  --bs-btn-hover-border-color: #A11F17;
  --bs-btn-active-bg: #A11F17;
  --bs-btn-active-border-color: #A11F17;
  --bs-btn-disabled-bg: var(--red);
  --bs-btn-disabled-border-color: var(--red);
}
.btn-outline-danger {
  --bs-btn-color: var(--red);
  --bs-btn-border-color: var(--red);
  --bs-btn-hover-bg: var(--red-w);
  --bs-btn-hover-color: var(--red);
  --bs-btn-hover-border-color: var(--red);
  --bs-btn-disabled-color: var(--red);
  --bs-btn-disabled-border-color: var(--red);
}

/* Feuilles posées : un filet, jamais une ombre.
   ⛔ `.toast` EST PARTIE LE 12.09.2026 : plus aucun écran ne pose cette classe
   (lot « Retour »), et un sélecteur sans porteur se lit comme un composant
   encore là. */
.card,
.modal-content,
.dropdown-menu,
.popover {
  border: 1px solid var(--rule-2);
  box-shadow: none;
}
.card { --bs-card-bg: var(--panel); --bs-card-cap-bg: var(--wash); }
.modal-content { --bs-modal-bg: var(--panel); }

/* ── LA LISTE FLOTTANTE DE SUGGESTIONS ─────────────────────────────────────
   ⛔ CE QUI SURVOLE SE DISTINGUE PAR SON FILET, PAS PAR UNE ÉLÉVATION
   (12.09.2026, lot Z). La liste du champ client était une `list-group`
   Bootstrap coiffée d'un `shadow-sm` : un cadre, des coins, un fond au survol
   et une ombre — quatre choses que la charte n'a pas. C'est une FEUILLE POSÉE,
   au même titre que `.dropdown-menu` et `.popover` juste au-dessus : fond
   `--panel`, un filet, rien d'autre.

   ⚠️ LA LIGNE ATTEINTE AU CLAVIER EST CELLE D'UN REGISTRE — fond `--wash` et
   filet d'encre dans la marge, comme `.wb-registre-ligne--choisie`, et non un
   bleu de sélection. Le filet est posé sur TOUTES les lignes, transparent par
   défaut, pour que la ligne courante ne décale pas son texte de trois pixels au
   moment où ↓ l'atteint. */
.wb-suggestions {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--panel);
  border: 1px solid var(--rule-2);
  max-height: 15rem;
  overflow-y: auto;
  /* ⚠️ AU-DESSUS DE CE QUI PEUT LA COIFFER : la liste s'ouvre parfois dans un
     dialogue, et cinq hôtes portaient déjà cette valeur en style en ligne — elle
     appartient au composant, pas à chaque écran. */
  z-index: 1080;
}
.wb-suggestions-item {
  display: block;
  width: 100%;
  margin: 0;
  padding: 4px 8px;
  background: none;
  border: 0;
  border-top: 1px solid var(--rule);
  border-left: 3px solid transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
}
.wb-suggestions-item:first-child { border-top: 0; }
button.wb-suggestions-item, .wb-suggestions-item[role="option"] { cursor: pointer; }
.wb-suggestions-item--active,
button.wb-suggestions-item:hover {
  background: var(--wash);
  border-left-color: var(--acc);
}

/* ⭐ ET LA PALETTE EST LA MÊME FEUILLE (13.09.2026, lot AA). `js/palette.js`
   bâtissait sa boîte sur `shadow-lg rounded-3` et ses lignes sur `list-group` :
   la dernière carte qui lévite du produit, et la dernière liste Bootstrap. Ses
   lignes sont des `.wb-suggestions-item`, sa boîte un fond `--panel` dans un
   filet — ni coin, ni ombre.
   ⚠️ SES LIGNES RESPIRENT UN PEU PLUS QUE CELLES D'UN CHAMP : la palette
   s'ouvre au milieu de l'écran et se parcourt à la flèche, là où une liste de
   suggestion se lit collée sous le champ qu'on est en train de remplir. */
.wb-palette-boite {
  background: var(--panel);
  border: 1px solid var(--rule-2);
}
.wb-palette-liste .wb-suggestions-item { padding: .35rem .75rem; }
.wb-palette-liste .lbl { padding-top: .6rem; }

/* ── L'échelle typographique (planche 02) ───────────────────────────────────
   La hiérarchie vient d'ELLE — plus jamais d'un fond, d'une ombre ou d'une
   couleur de plus. */

/* Le libellé réglé : 9,5 px, capitales interlettrées. C'est l'étiquette de
   TOUT — en-tête de colonne, clé d'un .kv, légende d'un bloc. */
.lbl {
  font-size: 12.5px;
  line-height: 1.4;
  font-weight: 600;
  color: var(--ink-3);
}

/* Le titre de bloc : 17 px. */
.ttl {
  font-size: 17px;
  line-height: 1.25;
  font-weight: 600;
  color: var(--ink);
}

/* Le titre d'écran : 34 px. */
.dsp {
  font-size: 22px;
  line-height: 1.2;
  font-weight: 700;
  color: var(--ink);
}

/* Les chiffres qui commandent l'écran : mono, tabulaires, trois corps. */
.fig,
.fig-2,
.fig-3 {
  font-family: var(--fonte-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  line-height: 1.1;
  color: var(--ink);
}
.fig   { font-size: 52px; }
.fig-2 { font-size: 30px; }
.fig-3 { font-size: 20px; }

/* Le chiffre courant : mono et tabulaire, sans changer de corps. Une référence,
   un compte, un numéro — tout ce qui se COMPARE d'une ligne à l'autre s'aligne
   sur la même chasse, sinon l'œil doit relire au lieu de balayer. */
.mono {
  font-family: var(--fonte-mono);
  font-variant-numeric: tabular-nums;
}

/* Toute colonne numérique : chiffres tabulaires, alignés à droite. */
.num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ── Composants de la planche ─────────────────────────────────────────────── */

/* L'état : un mot dans une étiquette à coin discret. Le rouge dit ce qui coûte. */
.et {
  display: inline-block;
  border: 1px solid var(--rule-2);
  border-radius: var(--coin-s);
  padding: 0 5px;
  font-size: 12px;
  line-height: 17px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--panel);
  white-space: nowrap;
}
.et--rouge { color: var(--red); border-color: var(--red); background: var(--red-w); }
.et--plein { color: #fff; background: var(--ink); border-color: var(--ink); }
/* ⭐ TROIS VARIANTES DE PLUS, ET AUCUNE COULEUR NOUVELLE (13.09.2026, lot AJ1).
   Le bac a retiré les huit teintes d'état du planning : l'état y est un MOT, et
   trois de ces mots ne se lisent pas au même volume que les autres.
   `--ok` est ce qui est FINI (« terminé non facturé »), `--att` ce qui manque
   (« sans facture »), `--non` ce qui est SORTI de la journée (« facturé »,
   « web ») — et pâlir est la bonne façon de le dire. Les trois reprennent les
   jetons d'état de la charte ; le rouge reste réservé à ce qui coûte du temps. */
.et--ok  { color: var(--etat-ok); border-color: var(--etat-ok); background: var(--etat-ok-w); }
.et--att { color: var(--etat-attention); border-color: var(--etat-attention); background: var(--etat-attention-w); }
.et--non { color: var(--ink-3); border-color: var(--rule); }

/* ── LA LÉGENDE : une SUITE D'ÉTATS, pas une rangée de pastilles ────────────
   ⛔ UNE LÉGENDE EXPLIQUE UNE COULEUR — ELLE N'A PAS À EN ÊTRE UNE (13.09.2026,
   lot AA). Les trois calendriers du produit — le planning de l'atelier, les
   locations, les absences de l'équipe — écrivaient chacun la leur : un fond de
   couleur, des coins, du gras, parfois du blanc dessus. Trois fois le même
   objet, trois apparences (règle 6). Or une légende ne dit que deux choses :
   VOICI LA TEINTE, VOICI CE QU'ELLE VEUT DIRE. Le carré porte la teinte, le mot
   reste du texte courant — l'item lui-même n'a ni fond, ni filet, ni coin.

   ⚠️ LA TEINTE SE TRANSMET, ELLE NE SE RECOPIE PAS : `--teinte` posée sur le
   carré, ou la classe d'état que la grille pose déjà (`.wb-etape-*`,
   `.wb-abs-type-*`). Une légende qui recopie la couleur de sa grille devient
   fausse le jour où la grille change, et personne ne s'en aperçoit — c'est le
   seul endroit de l'écran que l'œil ne compare jamais à rien.

   ⚠️ ET UN REPÈRE QUI N'EST PAS UNE COULEUR EST UN ITEM DU MÊME COMPOSANT :
   une icône, une trame, un numéro d'ordre se mettent À LA PLACE du carré. Sans
   cela, « urgent » et « planifié » se liraient comme deux valeurs d'une même
   échelle, alors que l'un se pose PAR-DESSUS l'autre. */
.wb-legende {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .25rem 1rem;
  color: var(--ink-2);
}
.wb-legende-item {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  color: var(--ink-2);
}
/* ⚠️ `position: relative` PARCE QU'UNE TRAME SE POSE EN `::after` (le travail
   interne du planning) : sans elle, la trame se calerait sur la page entière. */
.wb-legende-teinte {
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  inline-size: 10px;
  block-size: 10px;
  border: 1px solid rgb(0 0 0 / .18);
  border-radius: var(--coin-s);
  background: var(--teinte, transparent);
}
/* ⭐ LE CHIFFRE D'UN ITEM DE LÉGENDE (13.09.2026, lot AE) : ce que cette teinte
   pèse sur ce qu'on regarde. Une légende qui compte cesse d'être une notice et
   devient un récapitulatif — c'est gratuit, le chiffre est déjà calculé pour la
   grille. ⚠️ En chiffres tabulaires et en retrait d'encre : il accompagne le
   mot, il ne le remplace pas. */
.wb-legende-n {
  font-size: 12px;
  color: var(--ink-3);
}
/* Ce qui se superpose à une teinte, et n'en est donc pas une de plus. */
.wb-legende-sep {
  inline-size: 1px;
  block-size: 1em;
  background: var(--rule-2);
}

/* La paire clé-valeur : l'étiquette au-dessus, la valeur en courant. */
.kv { margin: 0; }
.kv > dt { font-size: 12.5px; font-weight: 400; color: var(--ink-3); }
.kv > dd { margin: 0 0 8px; color: var(--ink); }

/* Le raccourci clavier, affiché au pied de page (règle 2 de CLAUDE.md).
   ⛔ ET UNE TOUCHE DE L'AIDE EN LIGNE EST LA MÊME TOUCHE (21.09.2026, revue de
   l'aide). Sept sujets montrent une touche en `<kbd>` — que Bootstrap habille
   en INVERSE du corps (fond presque noir, texte clair) : la même touche se
   lisait donc en blanc sur noir dans l'aide et en encre sur papier partout
   ailleurs. Deux composants pour un objet, c'est la règle 6 ; la déclaration
   n'est pas recopiée, le sélecteur est ajouté ici. */
.k,
.wb-aide-corps kbd {
  display: inline-block;
  font-size: 12px;
  line-height: 16px;
  padding: 0 4px;
  border: 1px solid var(--rule-2);
  border-radius: var(--coin-s);
  color: var(--ink-3);
  background: var(--paper);
}

/* La jauge : une barre de 5 px, remplie en gris, en rouge au-delà. */
.jauge {
  position: relative;
  height: 5px;
  border-radius: var(--coin-s);
  overflow: hidden;
  background: var(--rule);
}
.jauge > i {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background: var(--ink-3);
}
.jauge > i.rouge { background: var(--red); }

/* Le tableau : tête en libellé réglé sous un FILET D'ENCRE, lignes sous un
   filet clair, l'alerte en lavis rouge. Vaut pour les .table Bootstrap des
   écrans WB 2.0 — Tabulator suit par ses propres jetons (--wb-dt-*, plus bas). */
.table {
  --bs-table-bg: transparent;
  --bs-table-color: var(--ink);
  --bs-table-border-color: var(--rule);
  --bs-table-striped-bg: var(--wash);
  --bs-table-hover-bg: var(--wash);
}
.table > thead th {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-3);
  border-bottom: 1px solid var(--rule-2);
}
.table > tbody td { border-bottom: 1px solid var(--rule); }
.table tr.alerte > td,
.table > tbody .table-danger > td {
  background: var(--red-w);
}

/* Le libellé de formulaire est le libellé réglé (même rôle, même composant). */
.form-label {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-2);
  margin-bottom: 3px;
}

/* Une case dans une rangée de champs se centre sur leur ligne de saisie. */
.wb-case-alignee { margin-top: 31px; }

/* ── Remap des jetons existants ─────────────────────────────────────────────
   app.css déclare --wb-dt-* plus haut dans la cascade : les redéclarer ici les
   fait basculer sur le Registre, et TOUTES les grilles Tabulator suivent sans
   toucher au JS. Il ne reste qu'une couleur sémantique de grille, --wb-dt-warn
   (le trait de la relance GED) — voir l'écart assumé n° 2 en tête de fichier. */
:root {
  --wb-dt-border: var(--rule);
  --wb-dt-border-strong: var(--rule-2);
  --wb-dt-surface: var(--panel);
  --wb-dt-surface-alt: var(--wash);
  --wb-dt-text: var(--ink);
  --wb-dt-text-muted: var(--ink-3);
  --wb-dt-accent: var(--ink-2);
  --wb-dt-accent-soft: var(--wash);
}

/* ── LE DOSSIER EN DEUX VOLETS — ce qui remplace le tiroir ─────────────────
   Décision du mandant : plus
   aucune fiche en offcanvas. Le remplaçant des maquettes est un volet
   maître-détail INTÉGRÉ — deux colonnes séparées d'un filet, pas une couche
   par-dessus.

   ⛔ CE N'EST PAS UN TIROIR REPEINT. Trois différences, et ce sont elles qui
   règlent les défauts du tiroir : le détail ne MASQUE pas la liste (on garde
   sous les yeux ce à quoi le panneau se rapporte), il ne piège pas le focus,
   et il ne pose ni fond assombri ni couche flottante.

   ⚠️ LA LARGEUR EST FIXE, LA LISTE PREND LE RESTE. L'inverse — un détail en
   pourcentage — donnait un formulaire qui rétrécissait quand on élargissait la
   fenêtre de la liste, ce que personne n'attend.

   ⚠️ EN DESSOUS DE 992 px, LES DEUX VOLETS S'EMPILENT. Un formulaire dans une
   colonne étroite est exactement le défaut qu'on corrige : sur un écran étroit,
   le détail passe sous la liste et prend toute la largeur. */
.wb-maitre-detail {
  display: flex;
  align-items: flex-start;
  gap: 0;
}
.wb-maitre-detail > .wb-maitre {
  flex: 1 1 auto;
  min-width: 0;   /* sans lui, une grille large pousse le volet hors de l'écran */
}
.wb-maitre-detail > .wb-detail {
  flex: 0 0 380px;
  max-width: 380px;
  border-left: 1px solid var(--rule);
  background: var(--panel);
  margin-left: 1rem;
  padding-left: 1rem;
  /* Le détail suit la liste au défilement, sans jamais la recouvrir. */
  position: sticky;
  top: 0.5rem;
  max-height: calc(100vh - var(--h-pied) - 1rem);
  overflow-y: auto;
}

/* ── LE VOLET QUI SAISIT — 40 rem, et c'est une mesure (14.09.2026) ────────
   ⛔ 380 px EST LA LARGEUR D'UNE LECTURE, PAS D'UNE SAISIE. Capture du mandant
   du 14.09.2026, `?page=client&no=649` sur un poste 4K à 150 % (2 560 px CSS) :
   le volet de création client y pose TROIS champs par ligne — les points de
   rupture de Bootstrap se mesurent sur la FENÊTRE, jamais sur la colonne qui
   les accueille, donc `col-sm-3/5/4` s'appliquent en entier dans 380 px. On y
   lisait « Civilité » sur 85 px, un nom de famille coupé en deux sur 140 px, et
   six lignes d'aide sous « Permis moto » dans 5/12 de colonne. Le volet
   véhicule fait pire : QUATRE `col-sm-3` sur une ligne.

   ⚠️ TROIS MESURES, ET PAS UNE DE PLUS : 380 px pour LIRE un détail (aperçu,
   résumé), 40 rem pour SAISIR un formulaire, 44 rem pour COMPARER deux fiches.
   ⚠️ 40, ET PAS 32 : à 32 rem (première livraison du jour), la seconde capture
   du mandant montrait encore un NPA coupé (« CW97TW UN ») et une civilité de
   113 px — trois champs par ligne veulent 640 px, soit 150 / 250 / 200.
   Un volet sans formulaire garde 380 px — une largeur qu'on prend sans en avoir
   besoin est prise à la liste d'à côté.

   ⚠️ LA CLASSE SE POSE DANS LE GABARIT, ET ELLE NE BOUGE JAMAIS : c'est la
   NATURE du volet, pas son état. `hidden` reste l'unique pilote de l'ouverture,
   et aucun script ne pose ni ne retire celle-ci.

   ⛔ ET ELLE S'ÉCRIT AVANT LE REPLI DE 992 px, délibérément. Sa spécificité est
   celle de la règle générale (deux classes) : à égalité, c'est l'ORDRE qui
   tranche, et sous 992 px c'est l'empilement qui doit gagner. Écrite après le
   `@media`, cette ligne aurait rendu 40 rem à un volet censé passer pleine
   largeur sous la liste. */
.wb-maitre-detail > .wb-detail--saisie {
  flex: 0 0 40rem;
  max-width: 40rem;
}
/* ⚠️ ET UN VOLET DE 40 rem S'EMPILE PLUS TÔT — 1 200 px, pas 992 : sous cette
   largeur, 640 px de volet ne laissent plus de quoi lire la fiche à côté
   (même raisonnement que la comparaison à 1 400 px). Mêmes déclarations que le
   repli général de 992 px, plus bas ; `:has()` fait passer le conteneur en
   `wrap` sans qu'aucun script ne pose de classe. */
@media (max-width: 1199.98px) {
  .wb-maitre-detail:has(> .wb-detail--saisie) { flex-wrap: wrap; }
  .wb-maitre-detail > .wb-detail--saisie {
    flex: 1 1 100%;
    max-width: none;
    border-left: 0;
    border-top: 1px solid var(--rule);
    margin-left: 0;
    padding-left: 0;
    padding-top: 1rem;
    position: static;
    max-height: none;
  }
}

/* La ligne de contexte d'un volet : POUR QUI l'on saisit, sous le titre.
   ⚠️ ELLE N'EST PAS UN SOUS-TITRE DÉCORATIF. « Nouvelle personne rattachée »
   dit ce qu'on fait ; cette ligne dit chez qui — la seule chose que le volet
   ne pouvait pas deviner avant qu'on la lui passe. Masquée (`hidden`) quand il
   n'y a pas de contexte : une ligne vide se lirait comme une donnée manquante. */
.wb-detail-contexte {
  margin: -.5rem 0 .75rem;
  font-size: 12px;
  color: var(--ink-2);
}

/* ⛔ `hidden` NE DOIT JAMAIS PERDRE, ET IL POUVAIT (01.09.2026). La règle
   n'était ni `!important` ni générale : elle ne valait que pour un `.wb-detail`
   ENFANT DIRECT d'un `.wb-maitre-detail`, et une future règle `display:` sur
   `.wb-detail` l'aurait emporté sur le défaut du navigateur — le volet
   resterait ouvert, par-dessus la colonne, sans que rien ne l'explique.
   ⚠️ ET LES TROIS GABARITS ANNONÇAIENT DÉJÀ CETTE FORME dans leur en-tête
   (« `.wb-detail[hidden]` porte `display:none !important` ») : le commentaire
   décrivait la règle qu'il fallait, pas celle qui était écrite. C'est la CSS
   qui a été mise d'accord avec eux, pas l'inverse — `hidden` est l'unique
   pilote de l'état d'un volet. */
.wb-detail[hidden] { display: none !important; }

.wb-detail-titre {
  display: flex;
  align-items: center;
  gap: .5rem;
  border-bottom: 1px solid var(--rule);
  padding-bottom: .5rem;
  margin-bottom: .75rem;
  color: var(--ink);
}
.wb-detail-pied {
  border-top: 1px solid var(--rule);
  padding: .75rem 0;
  margin-top: .75rem;
  display: flex;
  gap: .5rem;
  justify-content: flex-end;
}

@media (max-width: 991.98px) {
  .wb-maitre-detail { flex-wrap: wrap; }
  .wb-maitre-detail > .wb-detail {
    flex: 1 1 100%;
    max-width: none;
    border-left: 0;
    border-top: 1px solid var(--rule);
    margin-left: 0;
    padding-left: 0;
    padding-top: 1rem;
    position: static;
    max-height: none;
  }
}

/* ── LE VOLET QUI COMPARE — 44 rem, et c'est une mesure (12.09.2026, lot W) ─
   ⛔ 380 px EST LA LARGEUR D'UNE ENTITÉ, PAS DE DEUX. La revue des doublons y
   mettait DEUX fiches en vis-à-vis : 170 px chacune, un nom sur trois lignes,
   une adresse e-mail coupée — pendant que la liste gardait 1 600 px pour une
   ligne de texte et un chevron (capture du mandant, 12.09.2026). Une
   comparaison a deux colonnes de LECTURE : 20 rem chacune, plus la gouttière.

   ⚠️ ET L'EMPILEMENT ARRIVE PLUS TÔT — 1 400 px, pas 992. Sous cette largeur,
   44 rem de volet ne laissent plus de quoi lire la liste à côté.

   ⛔ ET C'EST LA LISTE QUI PASSE DESSOUS, PAS LA COMPARAISON. L'ordre habituel
   — détail sous liste — obligerait à faire défiler tout le registre après
   chaque clic pour voir ce qu'on vient d'ouvrir. Tant qu'aucune paire n'est
   ouverte, le volet est `hidden` : la liste occupe le haut de l'écran comme
   avant, et rien ne bouge. */
.wb-maitre-detail--comparaison > .wb-detail {
  flex: 0 0 44rem;
  max-width: 44rem;
}
@media (max-width: 1399.98px) {
  .wb-maitre-detail--comparaison { flex-wrap: wrap; }
  .wb-maitre-detail--comparaison > .wb-detail {
    order: -1;
    flex: 1 1 100%;
    max-width: none;
    /* Le filet sépare du VOISIN, et le voisin est en dessous : il descend. */
    border-left: 0;
    border-bottom: 1px solid var(--rule);
    margin-left: 0;
    margin-bottom: 1rem;
    padding-left: 0;
    padding-bottom: 1rem;
    position: static;
    max-height: none;
  }
}

/* ── LA COMPARAISON — clé, A, B ────────────────────────────────────────────
   Une grille de trois colonnes : la clé à gauche en libellé réglé, les deux
   valeurs en vis-à-vis. Ni fond, ni coin, ni pastille — ce qui distingue les
   lignes, c'est l'ENCRE : la clé passe au rouge quand les deux valeurs
   diffèrent, et les valeurs qui concordent passent en pâle. L'œil n'a alors
   qu'à lire ce qui reste noir. */
.wb-comparaison {
  display: grid;
  grid-template-columns: 7rem 1fr 1fr;
  column-gap: 1rem;
  row-gap: .35rem;
  align-items: baseline;
}
/* L'en-tête d'une colonne : le nom de la fiche, sa référence en mono dessous. */
.wb-comparaison-tete { align-self: end; }
/* ⚠️ LA RÉFÉRENCE ET LE LIEN SORTENT DU LIBELLÉ RÉGLÉ. `.lbl` met en capitales
   interlettrées : bon pour « FICHE A », illisible pour un numéro et pour
   « Voir la fiche ». Ils reprennent donc la casse et la chasse du texte
   courant, sous le libellé. */
.wb-comparaison-tete > .mono,
.wb-comparaison-tete > a {
  display: block;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
}
.wb-comparaison-tete > .mono { color: var(--ink-3); }
.wb-comparaison-tete > a { font-size: 12px; }
/* La clé du DIFFÉRENT est la seule chose rouge de la grille. */
.wb-comparaison-cle--differe { color: var(--red); }
/* Deux valeurs égales n'ont plus rien à apprendre : elles s'effacent. */
.wb-comparaison-egal { color: var(--ink-3); }
/* Une valeur absente : un tiret, de la même encre pâle qu'un vide ailleurs. */
.wb-comparaison-absent { color: var(--ink-3); }

/* ── LA LIGNE CHOISIE D'UN REGISTRE ────────────────────────────────────────
   ⚠️ UN FILET D'ENCRE DANS LA MARGE, PAS UNE OMBRE : la feuille de système
   n'a aucune `box-shadow` non nulle. Le filet est posé sur
   TOUTES les lignes, transparent par défaut, pour que la ligne choisie ne
   décale pas le texte de trois pixels au moment où on la choisit. */
.wb-registre-ligne > td:first-child { border-left: 3px solid transparent; }
.wb-registre-ligne--choisie > td { background: var(--wash); }
.wb-registre-ligne--choisie > td:first-child { border-left-color: var(--acc); }

/* Le compteur d'un titre : mono, pâle — il chiffre, il ne titre pas. */
.wb-compte {
  font-family: var(--fonte-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 400;
  color: var(--ink-3);
}

/* ── LA BARRE D'OUTILS DE PAGE ─────────────────────────────────────────────
   ⛔ CE QUE L'EN-TÊTE NE PORTE PAS (12.09.2026, lot Y). L'en-tête rend un
   titre, un compteur et des GESTES ; une période, un seuil, un sélecteur de
   caisse ou un champ de recherche ne sont pas des gestes — ce sont les
   CRITÈRES de ce qu'on regarde, et douze écrans les avaient glissés dans la
   rangée du titre, chacun avec ses classes. Ils vivent désormais sur une ligne
   à eux, juste sous l'en-tête, séparée du contenu par un filet.

   ⚠️ UN FORMULAIRE PORTE LA CLASSE LUI-MÊME quand la barre EST le formulaire :
   un conteneur de plus autour d'un `<form>` n'ajouterait qu'un niveau. */
.wb-outils {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 0 0 1rem;
  padding-bottom: .65rem;
  border-bottom: 1px solid var(--rule);
}
.wb-outils .form-label,
.wb-outils label { margin-bottom: 0; }

/* Le compte de la séance d'un poste à la douchette (réception, inventaire) :
   un seul composant, dans la barre d'outils, chiffre mono sur son libellé. */
.wb-poste-compteur {
  padding: .2rem .75rem;
  border: 1px solid var(--rule);
  text-align: center;
  line-height: 1.1;
}
.wb-poste-compteur-val {
  display: block;
  font-family: var(--fonte-mono);
  font-variant-numeric: tabular-nums;
  font-size: 28px;   /* lu debout, douchette en main : plus grand que .fig-3 */
  font-weight: 700;
  color: var(--ink);
}
.wb-poste-compteur-lbl {
  font-size: 12px;
  font-weight: 600;


  color: var(--ink-3);
}

/* ── LA FICHE — le dossier comme page imprimée ─────────────────────────────
   Le patron des maquettes, arbitré le 03.09.2026 : une MANCHETTE qui dit de
   qui il s'agit, une bande d'ALERTES pleine largeur juste dessous, puis le
   dossier en deux colonnes — les champs à gauche, l'INDEX à droite.

   ⛔ L'ORDRE N'EST PAS DÉCORATIF. Les alertes viennent avant tout le reste
   parce qu'elles ARRÊTENT ; les deux autres panneaux informent. Les ranger
   dans la colonne de droite les rendrait trouvables, pas visibles — ce qui
   n'est pas la même chose (le mandant, 12.08.2026).

   ⚠️ ET C'EST UN PATRON, PAS UNE PAGE. `.wb-fiche` habille aussi bien la fiche
   client que la fiche véhicule ou article : deux écrans de même nature ont le
   même composant (règle 6). Un écran qui recopierait ces valeurs en dur les
   verrait diverger au premier ajustement. */
.wb-fiche-manchette {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.25rem;
}
.wb-fiche-identite { flex: 1 1 auto; min-width: 0; }
.wb-fiche-surtitre {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: .15rem;
}
/* Le numéro se lit comme une référence : mono, tabulaire, en encre pâle. */
.wb-fiche-reference {
  font-size: 12px;
  color: var(--ink-3);
}
.wb-fiche-titre { margin: 0; overflow-wrap: anywhere; }
/* La NATURE d'une fiche client — personne ou entreprise — devant son nom : à la
   manchette, au titre du volet, dans la grille, la palette et les listes. Encre
   pâle : elle CLASSE, elle ne crie pas. ⛔ Jamais une couleur seule — l'icône
   porte son `aria-label` (`js/nature-personne.js`). */
.wb-nature-icone { color: var(--ink-3); margin-right: .3rem; }

/* ⛔ LA NATURE PROPOSÉE À LA SAISIE, ET SON `hidden` QUI NE DOIT JAMAIS PERDRE
   (07.09.2026). La ligne est un `.form-text` que le socle montre et cache par
   `el.hidden` — l'unique pilote de son état, comme pour les volets. La règle
   est écrite AVANT qu'une feuille pose un `display:` sur cette classe : sans
   elle, la proposition resterait à l'écran ce jour-là, et rien ne l'expliquerait
   (c'est mot pour mot ce qui est arrivé à `.wb-detail`, plus haut).
   ⚠️ Aucune couleur ici : la ligne prend celle de `.form-text`, et le bouton
   celle de `.btn-link` — toutes deux retaillées par les jetons du Registre. */
.wb-nature-propose[hidden] { display: none !important; }
.wb-fiche-titre-nature { font-size: .8em; }
/* Les classifieurs : ce qui CLASSE la fiche, sur une ligne sous son titre. */
.wb-fiche-classifieurs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .35rem .75rem;
  margin-top: .5rem;
}
.wb-fiche-actions {
  display: flex;
  gap: .5rem;
  flex-wrap: wrap;
  margin-left: auto;
}

/* La bande d'alertes : filet ROUGE pleine largeur, le seul de l'écran. */
.wb-fiche-alertes {
  border-top: 1px solid var(--red);
  padding-top: .35rem;
  margin-bottom: .5rem;
}
.wb-fiche-alertes > .lbl { color: var(--red); }

.wb-fiche-corps {
  display: flex;
  align-items: flex-start;
  gap: 0;
}
.wb-fiche-principal { flex: 1 1 auto; min-width: 0; }
/* ⚠️ UN FORMULAIRE SE BORNE (12.09.2026). Sur un écran de 2 000 px,
   « Nom commercial » faisait 1 800 px et le NPA 400 : des champs qu'on lit d'un
   coup d'œil devenaient des lignes à parcourir, et la page était blanche aux
   deux tiers.
   ⛔ ET « UNE FICHE À GRILLE GARDE TOUTE LA LARGEUR » ÉTAIT ÉCRIT ICI JUSQU'AU
   14.09.2026 (huitième revue) — c'était une exception que rien ne justifiait, et
   qu'un seul écran respectait déjà à l'envers : `?page=collaborateur` portait la
   borne, `?page=client`, `?page=vehicule` et `?page=article` non. Mesuré dans
   Chromium : « Adresse » y faisait 966 px à 1 366, puis 1 392 px à 1 920 ET à
   2 560 — un champ qui grandit avec l'écran, c'est-à-dire ce que le § « Largeur,
   hauteur, densité » refuse. Les six fiches portent maintenant la même borne,
   qui ne mord sur rien en dessous de ~1 420 px.
   ⭐ ET SUR UNE FICHE, ELLE SE POSE SUR LA COLONNE MAÎTRE (`.wb-maitre`), pas sur
   le corps : le corps borné, la colonne gardait ses 110 rem, et le volet ouvert
   s'installait à 384 px de la fiche sur un poste 4K — il cessait de se lire comme
   la colonne d'à côté. Posée sur la colonne, elle arrête d'un seul geste la
   manchette, les alertes, le corps et le pied ; une manchette rendue HORS de la
   colonne la porte elle-même (`?page=atelier`, `?page=location`), parce qu'un
   en-tête ne doit pas rétrécir à l'ouverture d'un volet. */
/* ⛔ ET SUR UNE PAGE DE RÉGLAGES, ELLE SE POSE SUR LE FORMULAIRE, PAS SUR SA
   COLONNE (12.09.2026) : bornée à la colonne seule, la barre d'action restait
   pleine largeur et « Enregistrer » partait au bord droit d'un écran de 2 000 px,
   à un mètre du dernier champ.
   64rem de formulaire, l'index de 340px et sa marge : la barre s'arrête sous
   l'index, le bouton sous la dernière colonne. */
.wb-fiche-bornee { max-width: calc(64rem + 1.75rem + 340px); }
/* ⚠️ ET SANS INDEX, LA BORNE EST CELLE DU FORMULAIRE SEUL (12.09.2026, lot V) :
   la plupart des écrans de réglages n'ont pas de colonne d'index, et leur
   laisser 1 392 px revient à leur laisser tout l'écran — le formulaire est le
   seul objet de la page. 64rem, c'est-à-dire la même colonne que chez les deux
   écrans qui ont un index. */
.wb-fiche-bornee--sans-index { max-width: 64rem; }
/* ⚠️ LARGEUR FIXE, MÊME RAISON QUE LE VOLET MAÎTRE-DÉTAIL : un index en
   pourcentage rétrécit quand on élargit la fenêtre — ce que personne
   n'attend. Le formulaire prend le reste. */
.wb-fiche-index {
  flex: 0 0 340px;
  max-width: 340px;
  border-left: 1px solid var(--rule);
  margin-left: 1.75rem;
  padding-left: 1.75rem;
}
@media (max-width: 1199.98px) {
  .wb-fiche-corps { flex-wrap: wrap; }
  .wb-fiche-index {
    flex: 1 1 100%;
    max-width: none;
    border-left: 0;
    border-top: 1px solid var(--rule);
    margin-left: 0;
    padding-left: 0;
    padding-top: 1rem;
  }
}
/* ⭐ UN PANNEAU QUI N'A RIEN À DIRE TIENT SUR SA LIGNE DE TITRE (23.09.2026, lot
   FICHEV) : « Prise en charge — Aucun tiers · Ouvrir un dossier… ». Le panneau
   marque sa ligne `.wb-vide` ; le libellé flotte, la ligne coule à côté. Les
   hauteurs réservées en ligne (`min-height`) sont parties : un bloc vide ne
   garde pas de blanc. */
.wb-fiche-index > .wb-bloc { clear: both; }
.wb-fiche-index > .wb-bloc:has(+ :has(> .wb-vide)) > .lbl {
  float: left;
  margin-right: .6rem;
  padding-top: .2em;
}
.wb-fiche-index :is([data-pec-form], [data-fin-form]):empty { display: none; }

/* ── LE COMPTEUR — le kilométrage, et la distance au prochain service ──────
   En tête du centre du dossier véhicule. La jauge va du dernier service fait (à gauche) au service proposé (le trait
   rouge, à droite) ; les chiffres viennent de `lib/vehicule_compteur.inc.php`,
   la largeur du remplissage est la seule valeur posée en ligne. */
.wb-compteur {
  flex: 0 0 15rem;
  display: grid;
  gap: .25rem;
  align-content: start;
}
.wb-compteur-valeur { display: flex; align-items: baseline; gap: .4rem; }
.wb-compteur-unite { color: var(--ink-3); }
.wb-jauge {
  position: relative;
  height: 10px;
  background: var(--wash);
  border-bottom: 1px solid var(--rule-2);
}
.wb-jauge > i {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  background: var(--ink-3);
  opacity: .55;
}
.wb-jauge::after {
  content: '';
  position: absolute;
  top: -3px;
  bottom: -3px;
  right: 0;
  width: 2px;
  background: var(--red);
}
.wb-jauge-legende {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  font-family: var(--fonte-mono);
  font-size: 12px;
  color: var(--ink-3);
}
.wb-jauge-legende .rouge { color: var(--red); }
@media (max-width: 1199.98px) {
  .wb-compteur { flex: 1 1 100%; }
}

/* ── LE PLI — ce qui ne se consulte pas à chaque passage, replié sous sa légende
   (sections repliées des dossiers, `App\Datatable::renderModalFieldsGrid()`). */
details.wb-pli > summary { list-style: none; cursor: pointer; }
details.wb-pli > summary::-webkit-details-marker { display: none; }
details.wb-pli > summary > span { display: block; }

/* ── LA RÉCEPTION QUI N'A PAS PU COUVRIR LA MOTO ────────────────────────────
   (28.09.2026) Datée après la 1re immatriculation, elle se replie
   (`VinDecoderTas::rangeParPremiereImmat()`) ; pli ouvert, elle se lit en
   retrait. ⚠️ SUR LES CELLULES, PAS SUR LA LIGNE : Bootstrap donne sa couleur
   à chaque cellule d'un tableau, et une couleur posée sur `<tr>` n'y descend
   pas. */
.wb-posterieure,
.table > tbody > tr.wb-posterieure > * { color: var(--ink-3); }

/* ── Le bloc : un libellé réglé sous un filet d'encre ──────────────────────
   La légende de section, rendue par `App\Datatable::legendeSection()`. Ni
   carte, ni ombre, ni couleur : c'est le filet qui sépare, et le libellé qui
   nomme. */
.wb-bloc {
  border-top: 1px solid var(--rule-2);
  padding-top: .35rem;
  margin-top: .75rem;
}
.wb-bloc > .lbl { display: block; }

/* ── L'APERÇU : le document tel qu'il s'imprimera ──────────────────────────
   Le seul endroit de la charte où un fond blanc est une INFORMATION et non une
   décoration : c'est du papier, posé sur le papier de l'écran. Il sert aux deux
   écrans de réglage du garage (identité, facturation) — même nature, même
   composant (règle 6) — et c'est `js/garage-apercu.js` qui le remplit à la
   frappe. */
.wb-apercu {
  background: var(--panel);
  border: 1px solid var(--rule);
  padding: .9rem 1rem;
  margin-top: .6rem;
}
.wb-apercu-logo { max-width: 100%; max-height: 44px; object-fit: contain; }
/* L'expéditeur : le corps du document, pas un libellé. */
.wb-apercu-expediteur { color: var(--ink); }
/* Ce que le document imprime en second rang — contact, IDE, compte. */
.wb-apercu-ligne { font-size: 12px; color: var(--ink-2); }
.wb-apercu-note {
  border-top: 1px solid var(--rule);
  padding-top: .5rem;
  margin-top: .9rem;
}

/* ── La barre d'action d'un écran de réglages ──────────────────────────────
   ⛔ EN PIED DE PAGE, JAMAIS FIXÉE À L'ÉCRAN : un bandeau flottant coûte
   une bande de hauteur sur un comptoir en 1366 px, et ces formulaires tiennent
   sur une page. À gauche ce qui reste à enregistrer, à droite la touche et le
   bouton — un seul par écran (`tests/Unit/RaccourcisActionsTest.php`).
   ⛔ ET RIEN NE S'OUVRE SOUS ELLE (14.09.2026) : une carte posée après le
   bouton semble relever d'un autre geste. Ce que le bouton n'enregistre pas
   passe au-dessus, et le bouton désigne son formulaire par `form=` quand la
   barre sort du `<form>`. */
.wb-barre-action {
  border-top: 1px solid var(--rule);
  padding-top: .75rem;
  margin-top: 1.5rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

/* ══ LA NOTE — ce qu'il faut savoir, sans boîte ═════════════════════════════
   ⛔ ELLE REMPLACE LES `alert alert-*` DES ÉCRANS WB 2.0 (12.09.2026). Cent
   trente-neuf boîtes Bootstrap se répondaient d'un écran à l'autre — jaune,
   bleue, grise, verte —, chacune avec son fond, son coin arrondi et son icône,
   et aucune ne disait ce que sa couleur promettait : le bleu portait aussi bien
   un mode d'emploi qu'un avertissement. La charte du Registre n'a ni pastille
   ni fond de couleur : une note est du TEXTE COURANT tenu par un filet d'encre
   à gauche, et on la lit parce qu'elle est là, pas parce qu'elle crie.

   ⚠️ DEUX TONS, PAS CINQ. L'encre pour ce qu'il faut savoir ; le rouge pour ce
   qui REFUSE ou BLOQUE — un droit manquant, un envoi impossible. Le succès
   d'une action n'est pas une note : c'est le retour du geste, à l'endroit du
   geste (`js/retour.js`). Un « rien à montrer » non plus : c'est une phrase
   (`js/etat.js`). */
.wb-note {
  border-left: 3px solid var(--rule-2);
  border-radius: 0 var(--coin) var(--coin) 0;
  background: var(--panel);
  padding: 6px 10px;
  margin: .75rem 0;
  color: var(--ink-2);
}
.wb-note--rouge { border-left-color: var(--red); background: var(--red-w); color: var(--ink); }
/* ⭐ UN TROISIÈME FILET, ET IL A FALLU LE JUSTIFIER (13.09.2026, lot AE). La
   note a DEUX tons — l'encre pour ce qu'il faut savoir, le rouge pour ce qui
   REFUSE —, et c'était assez tant que rien ne PRÉVENAIT sans bloquer.
   L'avertissement d'équipe du volet d'absence est exactement cela : « deux
   collègues de l'atelier sont déjà absents ce jour-là », et l'enregistrement
   passe. En encre, il se lirait comme un mode d'emploi et personne ne
   s'arrêterait ; en rouge, il se lirait comme un refus et on chercherait le
   bouton qui débloque. Le filet est donc celui de l'ATTENTION de la charte
   (`--etat-attention`), qui existe déjà et ne dit que ça. */
.wb-note--attention { border-left-color: var(--etat-attention); background: var(--etat-attention-w); color: var(--ink); }
/* Le titre d'une note, quand elle en a un : le mot qui la classe. */
.wb-note > .lbl { display: block; }

/* ── L'ÉTAT D'UNE ZONE : j'attends, ou il n'y a rien ───────────────────────
   Rendu par `js/etat.js`. Une PHRASE, jamais une animation ni une coche —
   une zone qui tourne 80 ms est un clignotement, et une liste vide n'est pas un
   succès. L'échec, lui, prend le filet rouge de la note. */
.wb-etat {
  color: var(--ink-3);
  margin: .5rem 0;
}

/* ── LA LECTURE : une colonne de texte, pas une ligne d'écran ──────────────
   ⛔ 44rem, ET C'EST UNE MESURE, PAS UN GOÛT (12.09.2026) : au-delà d'environ
   quatre-vingts caractères, l'œil perd le début de la ligne suivante et relit
   la même. Un sujet d'aide sur un écran de 2 000 px faisait des lignes de deux
   cents caractères — le texte était là, il ne se lisait pas. Seul le corps d'un
   texte suivi la porte ; un tableau et une grille n'ont pas de longueur de
   ligne. */
.wb-lecture { max-width: 44rem; }
/* ⚠️ LE ROUGE EST CELUI DE LA CHARTE, et il ne dit qu'une chose : ce qui n'est
   pas encore enregistré est ce qui peut se perdre. */
.wb-barre-action-etat { color: var(--red); }

/* ── LE RETOUR D'UN GESTE, À L'ENDROIT DU GESTE ────────────────────────────
   Rendu par `js/retour.js`. ⛔ Sur un écran de 2 000 px, le coin bas-droite
   n'est jamais là où l'œil est : la phrase se pose dans la barre d'action à
   côté du bouton, sur la ligne ou la carte du geste, ou — quand le geste n'a
   pas d'endroit (une impression partie, un courriel envoyé) — dans la ligne
   d'état de la page.
   ⚠️ RIEN DE COLORÉ HORS LE ROUGE DU REFUS : un succès est du texte courant.
   C'est l'HEURE qui porte l'information — « Enregistré » seul ne dit pas s'il
   parle du geste d'il y a dix minutes ou de celui d'il y a trois secondes. */
.wb-retour {
  color: var(--ink-2);
  margin: .35rem 0;
}
.wb-retour-heure {
  font-family: var(--fonte-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  margin-left: .4rem;
}
/* Un emplacement qui PORTE un retour cesse d'être le témoin rouge du non
   enregistré : c'est la même place, ce n'est plus la même chose qui s'y dit. */
.wb-barre-action-etat.wb-retour--pose {
  color: var(--ink-2);
  text-transform: none;
  letter-spacing: normal;
  font-size: inherit;
  font-weight: 400;
}
.wb-retour--rouge,
.wb-retour--rouge .wb-retour-texte { color: var(--red); }

/* La ligne d'état de la page : sous le titre, à gauche, elle garde le DERNIER
   événement et son heure jusqu'au suivant. Elle ne clignote pas et ne se ferme
   pas seule — ce qui est parti à l'imprimante reste vrai. */
#wb-page-etat {
  margin: 0 0 .5rem;
  color: var(--ink-2);
}
#wb-page-etat > .lbl { margin-right: .5rem; }

/* ── Ce qu'un retour porte EN PLUS de sa phrase ────────────────────────────
   Deux retours ne sont pas des phrases à lire mais des choses à FAIRE : le
   « Annuler » d'un rendez-vous déplacé, et le lien vers le message qui vient
   d'arriver. Ils vivent DANS la note, à côté de l'heure — au même endroit que
   le reste du retour, donc là où l'œil est déjà. */
.wb-retour-lien {
  margin-left: .5rem;
  color: var(--ink);
}
.wb-retour-action {
  margin-left: .5rem;
  vertical-align: baseline;
}

/* La note de refus accrochée à une ligne, une carte ou une commande : c'est la
   `.wb-note--rouge` de la charte, resserrée pour tenir dans une carte. */
.wb-retour-note {
  margin: .35rem 0 0;
  padding-left: .4rem;
  white-space: normal;
}
.wb-barre-action-droite {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: .75rem;
}

/* « Le dossier » : ce qui se rattache à la fiche, et combien. Une ligne, un
   filet, le chiffre à droite en mono tabulaire — la colonne de chiffres se lit
   d'un coup d'œil parce qu'ils sont alignés. */
.wb-dossier { list-style: none; margin: 0; padding: 0; }
.wb-dossier > li { border-bottom: 1px solid var(--rule); }
/* ⭐ LA RUBRIQUE À ZÉRO EST DU TEXTE, PAS UN LIEN (21.09.2026, lot UXK2, K2) :
   « Locations 0 » menait à une liste filtrée vide. Même ligne, même place, même
   compte — l'encre secondaire dit qu'il n'y a rien à ouvrir, et la ligne reste
   lisible : « il n'y en a pas » se lit, une rubrique absente ne se lit pas. */
.wb-dossier > li > a,
.wb-dossier > li > .wb-dossier-nul {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  padding: .5rem 0;
  color: var(--ink);
  text-decoration: none;
}
.wb-dossier > li > .wb-dossier-nul { color: var(--ink-3); }
.wb-dossier > li > a:hover { color: var(--ink-2); text-decoration: underline; }
.wb-dossier > li > a > :first-child,
.wb-dossier > li > .wb-dossier-nul > :first-child { flex: 1 1 auto; }
.wb-dossier .mono { font-size: 12px; color: var(--ink-3); }
/* La liste d'une carte se tient à la marge de son en-tête ; le cadre ferme la dernière ligne. */
.card > .wb-dossier { padding: 0 1rem; }
.card > .wb-dossier > li:last-child { border-bottom: 0; }

/* ══ « PERSONNES RATTACHÉES » — la liste d'une entreprise ═══════════════════
   ⛔ UNE LISTE, PAS UN TABLEAU ET PAS DES CARTES. Un tableau demanderait des
   en-têtes pour quatre colonnes dont trois se lisent d'elles-mêmes (un nom, un
   rôle, un téléphone) ; des cartes mettraient un cadre autour de chaque
   personne, là où la charte ne connaît que le filet. Une ligne par personne,
   séparée par un filet — comme « Le dossier », trois blocs plus haut.
   ⚠️ AUCUNE COULEUR EN DUR : la marque « Contact » est un `.et`, le rôle et les
   coordonnées prennent les encres secondaires du Registre. */
.wb-rattaches { list-style: none; margin: 0 0 .75rem; padding: 0; }
.wb-rattaches > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  padding: .4rem 0;
  border-bottom: 1px solid var(--rule);
}
/* ⚠️ LE LIEN DE LIGNE DES PANNEAUX, ET IL SERT AU-DELÀ DE CETTE LISTE : le
   collaborateur d'une fiche article, et depuis le 21.09.2026 (lot UXK2, K7) le
   véhicule écarté du panneau « Rappels de service », qui portait `.et` — un
   modèle de moto n'est pas un état (règle 6). Une quatrième déclaration
   identique aurait été une règle recopiée. */
.wb-rattache-nom { color: var(--ink); text-decoration: none; font-weight: 600; }
.wb-rattache-nom:hover { color: var(--ink-2); text-decoration: underline; }
.wb-rattache-role { font-size: 12px; color: var(--ink-2); }
.wb-rattache-coord { font-size: 12px; color: var(--ink-3); }
/* La colonne des gestes est poussée à droite : l'œil descend la liste des noms
   sans que la longueur des rôles ne la fasse serpenter. */
.wb-rattache-actions { margin-left: auto; display: flex; gap: .35rem; }
.wb-rattache-ajout > .lbl { display: block; margin-bottom: .25rem; }

/* ══ « PASSAGES ATELIER » — les dix derniers, sur la fiche véhicule ═════════
   ⛔ MÊME GRAMMAIRE QUE « LE DOSSIER » ET « PERSONNES RATTACHÉES » : une ligne
   par passage, un filet entre deux, aucune carte et aucun fond. Trois listes
   de même nature dans le même index doivent se lire de la même façon
   (règle ⭐ 6), sinon il faut réapprendre la mise en page à chaque bloc.
   ⚠️ LA DATE ET LE MONTANT SONT DES LIENS — l'ordre d'atelier et le document
   qui porte la somme (règle ⭐ 8). Le kilométrage et le mécanicien n'en sont
   pas : ils n'ont pas de source propre, et leur `title` le dit. */
.wb-passages { list-style: none; margin: 0 0 .5rem; padding: 0; }
.wb-passages > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem;
  padding: .4rem 0;
  border-bottom: 1px solid var(--rule);
}
.wb-passage-date { color: var(--ink); text-decoration: none; font-weight: 600; }
.wb-passage-date:hover { color: var(--ink-2); text-decoration: underline; }
/* Le libellé du service prend la place qui reste : c'est la seule longueur
   variable de la ligne, et la pousser à gauche garde le montant aligné. */
.wb-passage-quoi   { flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--ink-2); }
.wb-passage-repere { font-size: 12px; color: var(--ink-3); }
.wb-passage-montant {
  margin-left: auto;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-decoration: none;
}
a.wb-passage-montant:hover { color: var(--ink-2); text-decoration: underline; }
.wb-passages-tout { display: inline-block; margin-top: .25rem; }

/* La trace au pied du dossier : d'où vient cette fiche, et de quand. */
.wb-fiche-provenance {
  border-top: 1px solid var(--rule);
  padding-top: .35rem;
  margin-top: 1.25rem;
  font-size: 12px;
  color: var(--ink-3);
}

/* ══ LE MENU LATÉRAL (A2) : rail de 52 px, 272 px déplié ══════════════════
   Clair, l'item actif porte le trait marque à gauche. Replié, les écrans d'une
   section s'ouvrent en volant au survol ou au focus ; déplié, sous la section. */
.wb-shell {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  min-height: 100vh;
}
.wb-shell.wb-menu-deplie { grid-template-columns: 272px minmax(0, 1fr); }

.wb-rail {
  position: sticky;
  top: 0;
  z-index: 1030;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 6px;
  background: var(--wash);
  border-right: 1px solid var(--rule);
}
.wb-rail-espace { flex: 1 1 auto; }
.wb-rail-lib { display: none; }
.wb-menu-deplie .wb-rail-lib { display: inline; }

.wb-rail-marque {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 5px 10px;
  color: var(--ink);
  text-decoration: none;
  --wb-logo-accent: var(--marque);
}
.wb-rail-marque:hover { text-decoration: none; }
.wb-rail-logo {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 7px;
  background: var(--marque);
  color: #fff;
  font-weight: 700;
  font-size: 15.5px;
}
.wb-rail-marque .wb-rail-lib { min-width: 0; line-height: 1.1; }
.wb-rail-marque svg,
.wb-rail-marque img { display: block; height: 16px; width: auto; }
.wb-rail-marque small { display: block; margin-top: 3px; font-size: 12px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.wb-rail-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 40px;
  height: 40px;
  padding: 0;
  justify-content: center;
  border: 0;
  border-radius: var(--coin-l);
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: 14.5px;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.wb-rail-item .bi { font-size: 18px; line-height: 1; }
.wb-rail-item:hover { background: var(--rule); color: var(--ink); text-decoration: none; }
.wb-rail-item.wb-actif {
  background: var(--panel);
  color: var(--ink);
  box-shadow: inset 3px 0 0 var(--marque);
}
.wb-menu-deplie .wb-rail-item {
  width: auto;
  height: 34px;
  justify-content: flex-start;
  padding: 0 10px;
  color: var(--ink-2);
}
.wb-menu-deplie .wb-rail-item.wb-actif { color: var(--ink); font-weight: 600; }
.wb-rail-item .wb-hint { display: none; }
.wb-menu-deplie .wb-rail-item .wb-hint { display: inline-block; margin-left: auto; }

/* Le compteur : la pastille marque, sur l'angle replié, en bout de ligne déplié. */
.wb-rail-n {
  position: absolute;
  top: 3px;
  right: 2px;
  min-width: 14px;
  padding: 0 3px;
  border-radius: 7px;
  background: var(--marque);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 14px;
  text-align: center;
}
.wb-menu-deplie .wb-rail-n { position: static; margin-left: auto; font-size: 12px; line-height: 16px; padding: 0 6px; border-radius: 8px; }

.wb-rail-groupe { position: relative; }
.wb-rail-sous {
  display: none;
  position: absolute;
  top: -6px;
  left: calc(100% + 7px);
  z-index: 1031;
  min-width: 220px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--rule-2);
  border-radius: var(--coin-l);
  flex-direction: column;
  gap: 1px;
}
.wb-rail-sous::before { content: ""; position: absolute; top: 0; bottom: 0; left: -9px; width: 9px; }
.wb-rail-groupe:hover > .wb-rail-sous,
.wb-rail-groupe:focus-within > .wb-rail-sous { display: flex; }
/* Sous l'espace (Réglages), le volant s'ouvre vers le haut : vers le bas, il passait sous l'écran,
   la page gagnait un ascenseur, le survol se perdait et le volant clignotait. */
.wb-rail-espace ~ .wb-rail-groupe > .wb-rail-sous { top: auto; bottom: -6px; }
.wb-rail-sous-titre { padding: 4px 10px 6px; font-size: 12.5px; font-weight: 600; color: var(--ink-3); }
.wb-rail-sous-item {
  padding: 5px 10px;
  border-radius: 6px;
  color: var(--ink-2);
  font-size: 14px;
  text-decoration: none;
  white-space: nowrap;
}
.wb-rail-sous-item:hover { background: var(--wash); color: var(--ink); text-decoration: none; }
.wb-rail-sous-item.wb-actif { background: var(--acc-w); color: var(--ink); font-weight: 600; }
.wb-rail-sous-item.wb-a-venir { color: var(--ink-3); cursor: default; }
.wb-rail-sous-item.wb-a-venir:hover { background: none; }

/* Déplié : les écrans de la section active sous elle, les autres en volant. */
.wb-menu-deplie .wb-rail-groupe.wb-actif > .wb-rail-sous {
  display: flex;
  position: static;
  min-width: 0;
  padding: 2px 0 6px;
  background: none;
  border: 0;
}
.wb-menu-deplie .wb-rail-groupe.wb-actif .wb-rail-sous-titre { display: none; }
.wb-menu-deplie .wb-rail-groupe.wb-actif .wb-rail-sous-item { padding: 4px 10px 4px 38px; }
/* La touche se cale à droite ; si le libellé ne lui laisse pas la place, elle passe à la ligne,
   masquée : le volet garde sa largeur et le raccourci reste dans l'infobulle. */
.wb-menu-deplie .wb-rail-groupe.wb-actif .wb-rail-sous-item { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 6px;
  height: 29px; overflow: hidden; }
.wb-menu-deplie .wb-rail-groupe.wb-actif .wb-rail-sous-item .wb-hint { margin-left: auto; }
.wb-menu-deplie .wb-rail-groupe.wb-actif .wb-rail-sous-item.wb-actif { background: var(--panel); }

.wb-rail-recherche { cursor: text; }
.wb-menu-deplie .wb-rail-recherche { border: 1px solid var(--rule-2); background: var(--panel); color: var(--ink-3); margin-bottom: 6px; }
.wb-rail-plier .bi { transition: transform .18s; }
.wb-menu-deplie .wb-rail-plier .bi { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .wb-rail-plier .bi { transition: none; } }

.wb-rail .dropdown-menu { margin-left: 6px !important; }
.wb-notif-count {
  min-width: 20px;
  padding: 0 .3rem;
  border: 1px solid var(--rule-2);
  border-radius: 8px;
  color: var(--ink-3);
  font-size: 12px;
  font-weight: 600;
  text-align: center;
}
.wb-notif-count-actif { border-color: var(--marque); background: var(--marque); color: #fff; }
.wb-main { min-width: 0; overflow-x: clip; }

/* La barre de page (A2 « outils ») : un panneau blanc sous un filet, de bord à
   bord ; le fond déborde à droite d'une page plafonnée. */
.wb-pagebar {
  position: relative;
  isolation: isolate;
  margin: 0 -1rem .9rem;
  padding: 8px 1rem;
}
.wb-pagebar::before {
  content: "";
  position: absolute;
  inset: 0 -100vw 0 0;
  z-index: -1;
  background: var(--panel);
  border-bottom: 1px solid var(--rule);
}
.wb-pagebar:has(> .wb-onglets) { padding-bottom: 0; }
.wb-pagebar > .wb-onglets { border-bottom: 0; margin-top: 0; }
.wb-page-header h1 { font-size: 17px; font-weight: 700; }
/* Un écran qui porte sa propre manchette (tableau de bord, fiche) n'a pas de bande. */
.wb-pagebar:not(:has(h1, .wb-onglets)) { padding-block: 0; margin-bottom: 0; }
.wb-pagebar:not(:has(h1, .wb-onglets))::before { content: none; }
/* Sur une fiche, le « ? » flotte à droite de la manchette, qui lui laisse sa place.
   ⚠️ Pas de `:has()` dans un `:has()` : le navigateur rejette la règle entière. */
.wb-pagebar--aide-flottante > .wb-pagebar-haut { position: absolute; top: 22px; right: 1rem; }
.wb-page:has(> .wb-pagebar--aide-flottante) :is(.dossier-manchette, .wb-fiche-manchette) { padding-right: 1.75rem; }

/* ══ TROIS LARGEURS ET UN PLAFOND — le contenu d'une page ═══════════════════
   ⛔ LA RÉSOLUTION N'EST PAS UNE RESSOURCE À CONSOMMER (12.09.2026). Sur le
   poste de 2 000 px du comptoir, « Identité du garage » étirait le NPA à
   400 px et posait « Enregistrer » à un mètre du dernier champ ; une ligne de
   liste occupait 1 600 px pour six mots. LARGEUR FIXE, DENSITÉ VARIABLE : ce
   qui se LIT — corps de texte, largeur d'un champ, longueur d'une ligne,
   largeur d'un volet — ne grandit pas avec l'écran ; ce qui gagne de la place,
   c'est le NOMBRE de choses visibles côte à côte ou en hauteur.

   ⚠️ ALIGNÉ À GAUCHE, PAS CENTRÉ. Un contenu centré déplace la première
   colonne quand on change d'écran ou qu'on branche un second moniteur : au
   comptoir, la souris et l'œil vont toujours au même endroit.

   ⚠️ LE PLAFOND NE CONCERNE QUE LE CONTENU. La manchette, le bandeau et la
   sous-navigation sont hors de `.wb-page` et restent pleine largeur : une
   barre de navigation qui s'arrête au deux tiers de l'écran se lit comme une
   page cassée. */
.wb-page { max-width: 110rem; }

/* ── UN VOLET OUVERT S'AJOUTE AU PLAFOND, IL NE LE CONSOMME PAS (14.09.2026) ─
   ⛔ LE VOLET ROGNAIT LA FICHE. Capture du mandant, `?page=client&no=649` sur
   2 560 px CSS : ouvrir le volet de création poussait 396 px DANS les 110 rem,
   la fiche perdait d'un coup sa troisième colonne — pendant que 800 px
   restaient blancs à droite. Or la règle du 12.09.2026 dit aussi l'inverse de
   ce qu'on en avait tiré : ce qui gagne de la place sur un grand écran, c'est
   le NOMBRE de choses côte à côte, et un volet ouvert est exactement cela.

   ⚠️ LE PLAFOND DE LA FICHE NE CHANGE PAS : 110 rem restent 110 rem, le volet
   s'ajoute À CÔTÉ, avec sa gouttière (`margin-left: 1rem`). Sur un poste de
   2 000 px, rien ne bouge — la place manque, et `flex` resserre la colonne
   maître comme aujourd'hui.

   ⚠️ `hidden` RESTE L'UNIQUE PILOTE : ces règles lisent l'état du volet là où
   il est écrit, dans le HTML. Aucune classe posée par un script, donc aucun
   second état à tenir d'accord. `:has()` est déjà employé par
   `css/collaborateurs.css`.

   ⛔ ET `.wb-page--pleine` DOIT L'EMPORTER, ce que l'ordre seul ne donnait PAS :
   `:has()` prend la spécificité de son argument, ces sélecteurs pèsent donc
   quatre classes contre une. Plutôt que de gonfler l'exception, on l'EXCLUT —
   une page sans plafond n'a rien à quoi ajouter le volet.

   ⚠️ L'ORDRE COMPTE ENTRE CES TROIS-LÀ : un volet de saisie et un volet de
   comparaison sont aussi des `.wb-detail`, donc les trois règles les visent à
   spécificité égale. La plus précise s'écrit en dernier. */
.wb-page:not(.wb-page--pleine):has(.wb-maitre-detail > .wb-detail:not([hidden])) {
  max-width: calc(110rem + 380px + 1rem);
}
.wb-page:not(.wb-page--pleine):has(.wb-maitre-detail > .wb-detail--saisie:not([hidden])) {
  max-width: calc(110rem + 40rem + 1rem);
}
.wb-page:not(.wb-page--pleine):has(.wb-maitre-detail--comparaison > .wb-detail:not([hidden])) {
  max-width: calc(110rem + 44rem + 1rem);
}

/* ⛔ ON S'AFFRANCHIT DU PLAFOND EN LE DÉCLARANT, JAMAIS PAR DÉFAUT : la route
   porte `pleineLargeur: true` (`app/routes.php`), `app/layout.php` pose la
   classe. Un écran qui montre un AXE — le planning d'atelier, dont la largeur
   EST du temps — gagne vraiment à s'étendre ; un formulaire, jamais. */
.wb-page--pleine { max-width: none; }

/* ── La poignée de rangement d'une liste ordonnée ──────────────────────────
   ⭐ 11.09.2026 (lot E) — `?page=config-categories-location` range son
   référentiel au glisser. La poignée ne porte AUCUNE couleur : elle est une
   icône Bootstrap, encrée comme le reste de la ligne. Ce qu'elle déclare tient
   en deux propriétés, et les deux sont fonctionnelles :
     · `cursor` dit que la ligne se prend — sans quoi rien ne l'annonce ;
     · ⛔ `touch-action: none` est INDISPENSABLE au tactile : sans lui, le
       navigateur interprète le glisser comme un défilement et la page bouge au
       lieu de la ligne. C'est la même raison qui l'a posé sur `.div_rdv`
       (`css/webbike.css`). */
.wb-clc-poignee {
  cursor: grab;
  touch-action: none;
  display: inline-block;
  padding: 0 .25rem;
}
.wb-clc-poignee:active { cursor: grabbing; }

/* ── L'écran se lit comme un document imprimé — et s'imprime tel quel ───────
   Le socle : chrome masqué, papier blanc, encre noire. Chaque écran migré
   ajoute son propre @media print par-dessus (décision d'exécution du Lot 6). */
@media print {
  nav,
  .btn,
  .dropdown-menu,
  .modal-backdrop {
    display: none !important;
  }
  /* ⛔ `.toast-container` EST PARTIE LE 12.09.2026, avec le message éphémère
     lui-même : plus un seul écran ne pose cette classe (lot « Retour »). Le
     retour d'un geste vit maintenant DANS la page — barre d'action, ligne,
     carte, ligne d'état —, et il s'imprime donc avec elle, ce qui est la bonne
     réponse : une trace datée sur un document imprimé se lit comme une trace.
     ⛔ `.wb-sidebar` A ÉTÉ RETIRÉE D'ICI LE 01.09.2026, avec un jour de retard
     sur la sidebar elle-même. Son commentaire disait « elle disparaîtra avec le
     bandeau de navigation du Registre ; d'ici là, l'impression n'a pas à
     l'attendre » — ce jour est venu, le sélecteur n'avait plus aucun porteur, et
     une règle sans porteur se lit comme un composant encore là.
     Le chrome horizontal est masqué vingt lignes plus haut. */
  /* Sans chrome, le contenu prend toute la feuille. */
  .wb-main { padding-top: 0 !important; }
  body {
    background: #fff;
    color: #000;
    font-size: 12px;
  }
  .table > thead th { border-bottom-color: #000; }
}

/* ══ L'HARMONIE DU BALISAGE — quatre rôles, quatre composants ══════════════
   ⭐ 13.09.2026. La charte neutralisait les coins et les ombres par jetons
   (`--bs-border-radius: 0`, `--bs-box-shadow: none`), mais le balisage
   continuait de les DEMANDER : soixante-douze `card shadow-sm`, quatorze
   `rounded*`, des `bg-white`/`bg-light`/`bg-body-tertiary` et vingt-huit
   `text-primary` d'icône. Une classe morte est une décision d'écran qui
   survit à la charte : elle ne se voit plus, mais elle revit au premier
   thème, et elle se recopie.

   ⚠️ CE QUI SUIT NE SONT PAS DES UTILITAIRES, CE SONT DES RÔLES. Un rôle qui
   n'avait pas de composant en reçoit UN, nommé par ce qu'il fait, et tous les
   écrans de ce rôle le portent (règle 6 de CLAUDE.md). */

/* Le titre d'une carte ou d'une section de formulaire. Il existait sous quatre
   écritures — `wb-card-title h6`, `h6 mb-3`, `ttl`, `h6` — dont trois lisaient
   l'échelle de Bootstrap au lieu de celle de la charte. C'est le corps de
   `.ttl`, plus la marge basse que tous portaient déjà.
   ⚠️ IL REDÉCLARE `.wb-card-title` DE `css/app.css`, qui posait 1,05 rem et
   `--bs-emphasis-color` : c'est l'ordre de chargement qui le fait gagner. */
.wb-card-title {
  font-size: 17px;
  line-height: 1.25;
  font-weight: 650;
  color: var(--ink);
  margin: 0 0 .5rem;
}

/* Le bloc de code : une commande à recopier, une trace à lire. Le lavis du
   retrait et un filet — ni coin, ni ombre. Il sert les écrans de réglage
   (décodeur VIN, GED, technique) ET le corps de l'aide en ligne : même
   nature, même composant. */
.wb-code {
  font-family: var(--fonte-mono);
  font-size: 12px;
  background: var(--wash);
  border: 1px solid var(--rule);
  padding: .5rem .75rem;
  margin: 0;
  overflow-x: auto;
}

/* La zone de dépôt d'un fichier : quatre écrans d'import la recopiaient, avec
   quatre jeux de classes et un `border-dashed` QUI N'EXISTE PAS dans Bootstrap
   — le pointillé promis était un trait plein depuis le début.
   ⚠️ LE FILET EST `--rule-2`, PAS `--rule` : un pointillé du filet clair sur le
   papier de la page ne se trouve pas du regard à 1 366 px, et une zone de dépôt
   qu'on ne trouve pas est un champ de fichier de moins.
   L'état « le fichier survole » est le seul moment où elle s'encre. */
.wb-depot {
  display: block;
  border: 1px dashed var(--rule-2);
  background: var(--paper);
  padding: 1.25rem 1rem;
  text-align: center;
  cursor: pointer;
}
.wb-depot--actif {
  border-color: var(--acc);
  background: var(--wash);
}
/* Le champ de fichier est `visually-hidden` derrière son `<label>` : le trait
   du focus clavier se pose sur la zone, qu'on voit (27.09.2026). */
.wb-depot:has(+ .visually-hidden:focus-visible) {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
}

/* Ce qu'on vient de choisir dans une rangée de cartes — le pendant de
   `.wb-registre-ligne--choisie` pour ce qui n'est pas une ligne de tableau.
   C'est une SÉLECTION EN COURS, pas un état de donnée : elle prend l'encre et
   le lavis de l'actif, jamais une couleur d'accent. */
.wb-choisi {
  border-color: var(--actif);
  background: var(--actif-w);
}

/* ══ LE SURVOL D'UNE LIGNE APPARTIENT À LA CHARTE ══════════════════════════
   ⭐ 13.09.2026. `table-hover` vivait sur dix-sept écrans à tableau sur
   quarante-quatre : deux tableaux de même nature, à un clic l'un de l'autre,
   ne répondaient pas pareil sous le pointeur, et rien ne disait lequel avait
   raison. Le survol n'est pas une décision d'écran, c'est une AIDE DE LECTURE :
   à 1 366 px une ligne de registre traverse toute la feuille, et le lavis sous
   le pointeur est ce qui tient l'œil sur la ligne qu'on lit. Il vaut donc pour
   toute `.table`, et le balisage cesse de le demander.

   ⚠️ IL PASSE PAR LE JETON D'ÉTAT DE BOOTSTRAP, PAS PAR UN FOND. Une cellule
   peint déjà `--bs-table-bg` et se laisse recouvrir par `--bs-table-bg-state` ;
   un `background` posé ici passerait devant la rayure ET devant l'alerte.
   ⚠️ ET L'ALERTE GARDE SON LAVIS SOUS LE POINTEUR : une ligne rouge qui pâlit
   au moment où on la vise, c'est le signal qui s'éteint quand on le regarde.
   Le `table-hover` de Bootstrap l'effaçait ; la charte ne l'efface plus.
   ⚠️ NI LA TÊTE NI LE PIED NE SE SURVOLENT : on ne pointe pas un en-tête, et un
   total n'est pas une ligne où l'on va. */
.table > tbody > tr:hover > * {
  --bs-table-bg-state: var(--bs-table-hover-bg);
}
.table > tbody > tr.alerte:hover > td,
.table > tbody > tr.table-danger:hover > td {
  --bs-table-bg-state: var(--red-w);
}

/* ══ 13.09.2026 — CE QUE LA REVUE AU NAVIGATEUR A VU (lot BQ) ═══════════════
   Treize composants livrés ce jour ont été rendus à 1 366 px dans Chromium et
   REGARDÉS. Ce qui suit est ce que la capture a montré — pas ce que la lecture
   du code laissait supposer. */

/* ── LA FONTE DE LA CHARTE N'ATTEIGNAIT NI LES LIENS, NI LES CELLULES ───────
   ⛔ LA MOITIÉ DU PRODUIT S'ÉCRIVAIT DANS UNE AUTRE FONTE, ET PERSONNE NE
   L'AVAIT VU. `css/webbike.css` — la feuille du legacy, chargée sur TOUS les
   écrans — pose `body, th, td, a { font-family: system-ui; font-size: 14px }`.
   La charte redéclarait `body` et s'arrêtait là : chaque `<a>`, chaque `<th>` et
   chaque `<td>` d'un écran WB 2.0 rendait en system-ui 14 px pendant que la
   phrase autour rendait en Archivo 13 px. La capture du bloc « Contact de
   retour » (`?page=atelier`) le montre d'un coup d'œil : « En savoir plus » est
   plus GRAND et d'une autre fonte que la phrase qu'il termine.

   ⚠️ `font-size: inherit` ET NON UNE VALEUR : un lien prend la taille du texte
   où il est posé — 11,4 px dans un `.form-text`, 13 px dans un paragraphe, la
   taille du tableau dans une cellule. Une valeur fixe ici recasserait
   `.wb-svc-matrix` (0,85 rem) et rouvrirait la même faute à l'envers.

   ⚠️ ET LA SPÉCIFICITÉ EST CELLE DE LA RÈGLE QU'ELLE CORRIGE (0,0,1) : c'est
   l'ORDRE de chargement qui la fait gagner sur `webbike.css`, et n'importe
   quelle classe — `.lbl`, `.et`, `.wb-svc-corner`, `.table > thead th` — la bat
   sans avoir à être touchée. */
a,
th,
td {
  font-family: var(--fonte);
  font-size: inherit;
}

/* ── UN LIEN POSÉ DANS UNE PHRASE SE SOULIGNE ─────────────────────────────
   ⛔ « DANS UN PARAGRAPHE, UN LIEN NE SE RECONNAÎT PLUS » (le mandant,
   14.09.2026). `css/webbike.css` — le legacy, chargé sur tous les écrans — pose
   `a { text-decoration: none }` et ne rend le trait qu'au SURVOL : dans une
   phrase, un lien n'est alors qu'un mot d'encre parmi les mots d'encre, la
   charte ayant par ailleurs retiré le bleu. Il faut passer la souris dessus
   pour apprendre qu'il existe — c'est-à-dire ne jamais l'apprendre au clavier.

   ⭐ LES PORTEURS, ET RIEN D'AUTRE : le TEXTE COURANT. Un paragraphe (`p`),
   l'aide d'un champ (`.form-text`), une note de la charte (`.wb-note`), une
   colonne de lecture (`.wb-lecture` — c'est elle que porte la CARTE d'un sujet
   de l'aide en ligne, dont le corps est `.wb-aide-corps`). Les listes de ces blocs
   suivent d'elles-mêmes, par la descendance : une liste à puces dans un texte
   suivi EST du texte suivi.

   ⛔ ET LES EXCLUS SONT AUSSI UNE DÉCISION, chacun pour la même raison : le
   lien s'y reconnaît à sa PLACE, et le souligner ajouterait un trait à un écran
   qui en a déjà.
     · `td a` / `th a` — dans une grille, un lien se reconnaît à sa colonne, et
       quarante lignes soulignées font un écran rayé ;
     · `.btn` et `.btn-link` — un bouton-lien est un GESTE, pas une référence :
       il se reconnaît à sa place dans la barre d'actions. C'est le seul exclu
       qui ait besoin d'être ÉCRIT, parce qu'il arrive au milieu d'une phrase ;
     · la navigation — `.wb-onglet`, `.wb-bandeau-item`, `.wb-sousnav-item`,
       `.dropdown-item`, `.nav-link` : aucune ne vit dans un `p`, une
       `.form-text`, une `.wb-note` ni une `.wb-lecture`, et la règle ne les
       atteint donc jamais. Elles se reconnaissent à leur barre, où le filet dit
       déjà « vous êtes ici » ;
     · un libellé (`.lbl`) — c'est une étiquette, jamais une phrase ;
     · une ligne de `.wb-dossier` — la liste se lit à ses filets ; l'index de
       l'aide ne souligne que le titre du sujet (`.wb-aide-sujet`).
   ⚠️ LES EXCLUS SONT ÉCRITS EN `:not()` SUR LA RÈGLE, jamais en règle inverse :
   un `text-decoration: none` posé après aurait couru sur des liens qu'aucune de
   ces listes ne décrit, et la liste des porteurs aurait cessé de dire vrai.

   ⚠️ LE TRAIT EST SOBRE ET LA COULEUR NE BOUGE PAS : 1 px décollé de la ligne
   de base, sur l'encre du lien (`--bs-link-color`, déjà `--ink`). Un lien reste
   du texte — c'est sa forme qui le désigne, pas une seconde couleur. */
p a:not(.btn):not(.btn-link),
.form-text a:not(.btn):not(.btn-link),
.wb-note a:not(.btn):not(.btn-link),
.wb-lecture a:not(.btn):not(.btn-link):not(.wb-dossier a),
.wb-dossier .wb-aide-sujet {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

/* ⚠️ LES CHAMPS ET LES BOUTONS : ICI, LA FONTE SEULE. Le sélecteur du legacy
   (`input:not([type=radio]):not([type=checkbox]), select, button, textarea`)
   pèse 0,2,1 sur sa première branche — plus lourd que toute classe Bootstrap —,
   et il fixe AUSSI la taille : aucune classe ne pouvait plus la demander. Cette
   règle-ci rend la FONTE, à spécificité égale et plus tard dans la cascade —
   la géométrie ne bouge pas d'un pixel ; la TAILLE est rendue plus bas, par
   `--taille-champ` (§ du 14.09.2026). */
input:not([type=radio]):not([type=checkbox]),
select,
button,
textarea {
  font-family: var(--fonte);
}

/* ── LA POSITION ACTIVE D'UN GROUPE DE BOUTONS EST DE L'ENCRE, `.active` AUSSI
   ⛔ LA RÈGLE EXISTAIT ET NE COUVRAIT QU'UNE MOITIÉ DES GROUPES. Le 12.09.2026
   (lot Z) a décidé que « la position qui vaut est de l'encre » et l'a écrite
   pour `.btn-check:checked` — la forme à cases cachées. Sept écrans choisissent
   pourtant leur position par la classe `.active` posée en JS : les six degrés
   de rappel, la vue des collaborateurs, la période des locations, les vues
   livraisons et calendrier de location, le rapport mensuel, les statistiques.
   Sur ceux-là, le bouton choisi rendait `--bs-btn-active-bg`, c'est-à-dire
   `--wash` — EXACTEMENT le fond du survol. La capture des six degrés le montre :
   on ne voit pas lequel est ouvert. Même geste, même apparence (règle 6).

   ⚠️ LA RÈGLE `.btn-check` RESTE ÉCRITE AILLEURS (plus haut dans la feuille) :
   celle-ci ne la remplace pas, elle ajoute la forme que le JS emploie.
   ⚠️ ET LES DEUX VARIANTES DE FILET SONT NOMMÉES, comme là-haut (14.09.2026). */
.btn-outline-secondary.active,
.btn-outline-primary.active {
  --bs-btn-active-bg: var(--ink);
  --bs-btn-active-border-color: var(--ink);
  --bs-btn-active-color: #fff;
}
/* ⛔ ET LE COMPTE QUE LE BOUTON PORTE SUIT SON ENCRE, sinon la correction
   ci-dessus RETIRE une information : `.wb-compte` est en `--ink-3`, c'est-à-dire
   gris foncé — posé sur le fond d'encre du bouton choisi, « 12 » devenait
   illisible (vu au rendu, capture des six degrés). Un chiffre sur le bouton
   qu'on vient d'ouvrir est justement celui qu'on regarde. */
.btn-check:checked + .btn-outline-secondary .wb-compte,
.btn-check:checked + .btn-outline-primary .wb-compte,
.btn-outline-secondary.active .wb-compte,
.btn-outline-primary.active .wb-compte,
.btn-primary .wb-compte {
  color: inherit;
}

/* ── LA BULLE DE PREMIÈRE VISITE : LE DERNIER COIN ARRONDI DU PRODUIT ───────
   ⛔ ET C'EST LA PREMIÈRE CHOSE QUE VOIT QUELQU'UN QUI ARRIVE. `.wb-search-coach`
   (`css/app.css`, posée par `js/palette.js`) portait `border-radius: .5rem`
   et une ombre portée d'un rem : la charte n'a ni l'un ni l'autre, et cette
   bulle-là est montrée UNE fois, à la première session, pour présenter la
   recherche. Elle reste une feuille posée sur l'encre — fond `--ink`, texte
   `--paper` — et sa flèche continue de désigner le champ ; ce sont le coin et
   l'auréole qui partent. C'est l'ordre de chargement qui le fait gagner sur
   `app.css`, comme pour `.wb-card-title`. */
.wb-search-coach {
  border-radius: var(--bs-border-radius-lg);
  box-shadow: none;
}

/* ── L'ANNONCE DES TOUCHES D'UNE FILE ──────────────────────────────────────
   ⛔ UN SOCLE, TROIS APPARENCES. `js/file.js` écrit la même ligne de touches
   sur les trois écrans de file, et les trois hôtes l'habillaient chacun à leur
   façon : `text-body-secondary` sur la qualité du fichier, l'encre pleine sur
   les deux autres ; 16 px de marge basse ici, 8 px là, 0 ailleurs. La capture
   des trois côte à côte montre deux gris et trois espacements pour une seule
   ligne. La ligne appartient au socle : son encre et son espacement aussi.

   ⚠️ `align-items: baseline` PARCE QUE `.k` EST UNE BOÎTE : une touche dessinée
   dans un filet et le mot qu'elle commande doivent poser sur la MÊME ligne de
   base, sinon la ligne ondule d'une entrée à l'autre.
   ⚠️ LA MARGE BASSE RESTE À L'HÔTE quand il porte autre chose que la légende
   (la revue des doublons y met aussi le lien « Aide ») : le composant ne connaît
   pas ses voisins. */
.wb-file-touches {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .25rem 1rem;
  color: var(--ink-2);
}

/* ══ 14.09.2026 — LES ENCRES SÉMANTIQUES ET LES TAILLES DE CHAMP (lot BW) ═══
   Deux fautes de la même famille : une classe de Bootstrap posée là où la
   charte a un jeton, et une règle du legacy qui confisque une classe. Les deux
   se voient au rendu, aucune ne se voit à la lecture. */

/* ── UNE CLASSE DE CHARTE PAR RÔLE D'ENCRE ─────────────────────────────────
   ⛔ CINQ COULEURS DE BOOTSTRAP PORTAIENT LE SENS, ET TROIS ÉTAIENT HORS
   CHARTE. Les écrans disaient l'erreur en `text-danger`, le fini en
   `text-success`, l'attente en `text-warning`, le secondaire en
   `text-secondary` — cent quatre-vingt-quatorze écritures. Rendues dans le
   navigateur, ces classes donnaient : le rouge du Registre (`--bs-danger-rgb`
   est bien remis aux jetons, celle-là allait), mais aussi le VERT de Bootstrap
   (#198754, quand `--etat-ok` vaut #4A7C59), son AMBRE (#ffc107, illisible sur
   le papier de la charte, quand `--etat-attention` vaut #A8791F) et son GRIS
   (#6c757d, quand l'encre secondaire est `--ink-2`, #4E555B). Trois teintes
   qui ne suivront pas le jour où la charte bouge, et qu'aucune lecture du code
   ne montre : `text-success` a l'air juste tant qu'on ne l'a pas vu.

   ⚠️ NOMMÉES PAR LE RÔLE, COMME LES JETONS QU'ELLES LISENT : `.wb-encre-rouge`
   est ce qui coûte du temps ou de l'argent — et une erreur en est —, non « ce
   qui est rouge ». Le jour où l'attention passe de l'ambre au brun, `--att`
   suit sans qu'on relise un écran.

   ⚠️ LE SECONDAIRE N'A PAS DE CLASSE ICI : c'est `text-body-secondary`, que
   Bootstrap fait déjà lire `--bs-secondary-color`, remis à `--ink-2` plus haut.
   Une classe maison de plus n'aurait été qu'un second nom du même pixel — et le
   registre du balisage refuse justement les rôles à deux noms.

   ⚠️ ET `text-info` N'A PAS DE SUCCESSEUR : il n'habillait que des icônes de
   mode (« dupliquer », « modifier », une suggestion d'adresse). Rien n'y est en
   attente ni fini — c'est de l'encre ordinaire, et le pictogramme dit le reste.

   ⚠️ LA CELLULE D'UN TABLEAU DEMANDE UNE LIGNE DE PLUS. `.table > :not(caption)
   > * > *` pose une couleur sur CHAQUE cellule, à 0,1,1 : une classe simple
   posée sur un `<td>` perd (les utilitaires de Bootstrap ne gagnaient que par
   leur `!important`). On passe donc par `--bs-table-color-state`, le jeton que
   cette règle lit en premier — le même détour que le survol plus haut. */
.wb-encre-rouge {
  color: var(--red);
  --bs-table-color-state: var(--red);
}
.wb-encre-ok {
  color: var(--etat-ok);
  --bs-table-color-state: var(--etat-ok);
}
.wb-encre-att {
  color: var(--etat-attention);
  --bs-table-color-state: var(--etat-attention);
}
/* ⚠️ ET SUR UN BOUTON-LIEN, L'ENCRE TIENT AU SURVOL. Trois gestes qui défont
   sont des `btn btn-link` — supprimer une vue de grille, annuler une ligne de
   réception, retirer une ligne de commande : `.btn:hover` pèse 0,2,0 et
   repeindrait le geste en encre au moment même où on le vise, là où
   `text-danger` tenait par son `!important`. Un geste destructif ne perd pas sa
   couleur juste avant le clic. */
.btn.wb-encre-rouge {
  --bs-btn-color: var(--red);
  --bs-btn-hover-color: var(--red);
  --bs-btn-active-color: var(--red);
}

/* ── LA TAILLE D'UN CHAMP LUI EST RENDUE, ET L'ÉCRAN SEUL LA DEMANDE ───────
   ⛔ LE LEGACY CONFISQUE LA TAILLE DE TOUS LES CHAMPS. Le sélecteur de
   `css/webbike.css` — `input:not([type=radio]):not([type=checkbox]), select,
   button, textarea { font-size: 14px }` — pèse 0,2,1 sur sa PREMIÈRE branche,
   plus lourd que toute classe : mesuré dans Chromium, le champ de scan de
   `?page=inventaire` — annoncé « XXL », lu debout à un mètre — rendait 14 px.
   La règle ci-dessous rend cette taille à l'écran par un JETON : une propriété
   personnalisée ne se dispute pas avec la cascade.

   ⛔ ET `form-control-lg` N'EN EST PLUS LA CLÉ (14.09.2026, décision du
   mandant). Le premier état de ce § faisait porter le jeton par
   `.form-control-lg` : la classe est alors DEVENUE VISIBLE là où elle dormait,
   et des champs « grands » sont apparus sur des écrans qui ne l'avaient jamais
   vue s'appliquer — le montant de l'acompte, les quantités de l'inventaire et
   de la réception. La classe est retirée de tout le balisage WB 2.0 (registre
   `tests/Unit/ChampsLiensEtOngletsTest.php`), et ce qui doit rester grand le
   demande PAR SON NOM : `.wb-inv-scaninput` et `.wb-rec-scaninput` — les deux bandes de
   scan, qui se lisent debout, à un mètre — et `.wb-inv-ligne-qte`, la
   correction d'une ligne comptée, qui se vise au doigt.

   ⚠️ LE JETON EST LU POUR TOUT `<input>`, PAS POUR UNE CLASSE, et c'est ce qui
   rend la promesse vraie. Tant que la règle nommait `-lg` et `-sm`, un écran
   qui posait `--taille-champ` sur sa classe n'était obéi QUE s'il portait aussi
   l'une des deux — un piège que rien ne signalait. Le repli est la valeur du
   legacy : sans jeton, rien ne bouge d'un pixel.

   ⚠️ ET CETTE RÈGLE-CI N'ATTEINT QUE LES `<input>` : les trois autres branches
   du sélecteur du legacy (`select`, `button`, `textarea`) ne pèsent que 0,0,1 et
   perdent contre Bootstrap. ⛔ CE § A LONGTEMPS AJOUTÉ « et ça ne change rien,
   elles rendent déjà 14 px » — C'ÉTAIT FAUX, et mesuré faux le 14.09.2026 : un
   `.btn`, un `.form-select`, un `<textarea>` et un `.input-group-text` rendaient
   16 px à côté d'un champ à 14. La suite est au § de la troisième revue, en fin
   de feuille. */
.form-control-sm { --taille-champ: .875rem; }
input:not([type=radio]):not([type=checkbox]) {
  font-size: var(--taille-champ, 14px);
}

/* ══ 14.09.2026 — CE QUE LA REVUE DES ÉCRANS ENTIERS A VU (lot BZ) ══════════
   Huit écrans de comptoir — la fiche d'ordre, le document, la fiche client, la
   grille des clients, la caisse du jour, le suivi des services, les
   notifications, le réapprovisionnement — rendus à 1 366 et 1 920 px dans
   Chromium, en-tête de page comprise, et REGARDÉS l'un à côté de l'autre. Ce
   qui suit est ce que la capture a montré, pas ce que la lecture du code
   laissait supposer. */

/* ── LE FILET QUI SÉPARE LE DOSSIER DE SON INDEX S'ARRÊTAIT EN CHEMIN ───────
   ⛔ `.wb-fiche-corps` alignait ses deux colonnes en `flex-start` : l'index —
   « Le dossier », « Rappels de service », « Documents » — est TOUJOURS plus
   court que la grille de champs à sa gauche, et son `border-left` s'arrêtait
   donc au milieu de la page. Mesuré sur `?page=client` à 1 366 px : 340 px de
   filet pour deux fois plus de formulaire, c'est-à-dire un trait qui commence
   en haut et finit dans le blanc, sans rien pour l'expliquer.

   ⚠️ `stretch` NE CHANGE QUE LA HAUTEUR DES COLONNES, PAS LEUR CONTENU : les
   deux enfants portent chacun leur propre flux, et aucun des deux n'est centré.
   Le filet court désormais sur toute la hauteur du dossier — la ligne de pliure
   d'une feuille, ce que la charte dessine partout ailleurs.
   ⚠️ ET SOUS 1 200 px, RIEN NE CHANGE : la media query du dessus empile les
   colonnes et remplace le filet gauche par un filet haut. */
.wb-fiche-corps { align-items: stretch; }

/* ── LE CHAMP DE SAISIE N'AVAIT PAS DE TRAIT DE FOCUS ──────────────────────
   ⛔ MESURÉ À LA TOUCHE Tab, DANS CHROMIUM, SUR LES HUIT ÉCRANS DE LA REVUE.
   Les liens, le « ? » de la barre, le champ en lecture seule
   (`.form-control-plaintext`) et la LISTE (`.form-select`) reçoivent le trait de
   la charte — `:focus-visible { outline: 2px solid var(--acc); outline-offset:
   1px }`. Un `<input class="form-control">` et un `<textarea>`, non :
   `outline: —`. ⛔ ET CE § A DIT « les boutons » ET « la case à cocher » PARMI
   LES SERVIS — C'ÉTAIT FAUX : le relevé du 14.09.2026 les a trouvés muets eux
   aussi, pour la même raison, et la correction est au § de la troisième revue.

   ⛔ ET C'EST BOOTSTRAP QUI L'ÉTEINT, PAS LA CHARTE. `.form-control:focus`
   déclare `outline: 0` — 0,2,0, plus lourd que `:focus-visible` (0,1,0) —, et
   `.form-select:focus` ne le fait PAS. D'où la panne la plus visible de la
   revue : dans une même rangée de formulaire, la liste montre où l'on est, le
   champ juste à côté ne montre rien. Ce qui restait — la bordure qui passe de
   `--rule` à `--ink-2` — est le signal le plus faible de l'écran, et il est posé
   là où il compte le plus : une fiche client en porte vingt-cinq.

   ⚠️ MÊME POIDS QUE LA RÈGLE QU'ELLE CORRIGE (0,2,0) et plus tard dans la
   cascade : c'est l'ORDRE qui la fait gagner, comme pour la fonte des liens et
   la taille des champs plus haut. Un écran qui dessine son propre focus dans son
   `<style>` — l'éditeur de lignes du document — reste maître : son bloc est rendu
   dans le corps, donc après cette feuille.
   ⚠️ ET LA BORDURE D'ENCRE RESTE : le trait dit « c'est ici », la bordure dit
   « ce champ est ouvert ». Les deux se lisent ensemble sur tous les autres
   contrôles depuis la charte. */
.form-control:focus-visible,
.form-select:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
}

/* ── L'ONGLET OUVERT EST ROUGE, LE FOCUS EST DE L'ENCRE ────────────────────
   ⛔ UNE COULEUR NON CHARTÉE SUR UNE BARRE DE NAVIGATION (ce § l'a dite « la
   seule qui restait » ; la case cochée en portait une autre, trouvée le
   14.09.2026 au soir et corrigée au § de la troisième revue).
   `.wb-onglet` (`css/app.css`, rendu par `App\PageBar::onglets()`)
   posait `border-bottom-color: var(--bs-primary)` sur l'onglet actif et un
   `outline` de la même teinte au focus. `--bs-primary` n'est retaillé NULLE
   PART par la charte : mesuré dans Chromium, le trait de l'onglet ouvert
   sortait en rgb(13, 110, 253) — le bleu de Bootstrap — sur chaque écran des
   Paramètres, des Outils et de la Caisse, deux pixels sous un titre d'encre.

   ⭐ ET C'EST LE ROUGE QUI DIT « VOUS ÊTES ICI » (14.09.2026, décision du
   mandant). La charte a DEUX marques de position, et elles ne se choisissent
   pas au hasard : le bandeau porte l'encre (`.wb-bandeau-item.wb-actif`, un
   module ouvert parmi douze), la sous-navigation porte LE rouge
   (`.wb-sousnav-item.wb-actif`, plus haut dans cette feuille). Un onglet de
   sous-section est de la même famille que la seconde — la barre qui découpe un
   écran en volets —, et deux barres voisines qui disent la même chose de deux
   couleurs différentes se lisent comme deux choses différentes (règle 6).

   ⛔ LE FOCUS, LUI, RESTE DE L'ENCRE — et il n'a plus de règle à lui. La marque
   de position et la marque du clavier ne peuvent pas être de la même couleur :
   sur un onglet ouvert, le rouge dirait deux fois, et l'un des deux mentirait.
   `css/app.css` ne dessine plus de focus ici : celui de la charte
   (`:focus-visible`, en tête de cette feuille) s'applique, sans coin arrondi et
   sans halo — le trait de tous les autres contrôles du produit. */
.wb-onglet.actif { border-bottom-color: var(--acc); }

/* ⭐ LES ONGLETS DES PARAMÈTRES SE RANGENT PAR FAMILLE (`App\PageBar::onglets()`) :
   une ligne par famille, son nom au-dessus de ses onglets, aligné sur le titre de
   la page ; les onglets, même repliés, sont décalés d'un cran sous le nom. */
.wb-onglets.wb-onglets--familles {
  flex-direction: column;
  flex-wrap: nowrap;
  border-bottom: 0;
}
.wb-onglets-ligne {
  display: flex;
  flex-wrap: wrap;
  gap: .15rem;
  padding-left: .75rem;
  border-bottom: 1px solid var(--rule);
}
.wb-onglets-famille {
  flex: 0 0 100%;
  margin-left: -.75rem;
  padding: .55rem 0 0;
  font-size: .78rem;
  font-weight: 600;
  color: var(--ink-2);
}

/* ══ 14.09.2026 — CE QUE LA TROISIÈME REVUE A VU (lot CR) ═══════════════════
   Dix écrans que les deux premières n'avaient pas ouverts — l'encaissement
   rapide, l'ouverture et la clôture de caisse, l'acompte pièces, le contrat de
   location, les trois grilles (véhicules, articles, pipeline pièces), la
   sécurité d'accès et la passerelle SMS —, rendus à 1 366, 1 920 et 2 560 px
   dans Chromium, parcourus À LA TOUCHE Tab, et REGARDÉS l'un à côté de l'autre.
   Ce qui suit est ce que la capture et le `getComputedStyle` ont montré. */

/* ── NI UN BOUTON NI UNE CASE N'AVAIT DE TRAIT DE FOCUS ─────────────────────
   ⛔ LES DEUX CONTRÔLES LES PLUS FRÉQUENTS DU PRODUIT ÉTAIENT MUETS SOUS LE
   CLAVIER. Parcouru à la touche Tab, `:focus-visible` vérifié vrai à chaque
   arrêt : un `<input>`, un `<select>` et un `<textarea>` portent le trait de la
   charte ; un `.btn`, un `a.btn`, une case, une radio et un interrupteur ne
   portaient RIEN — `outline: none 0px`, `box-shadow: none`. Sur un écran de
   réglages qui n'est QUE des cases et un bouton (`?page=config-securite`,
   vingt-cinq lignes), passer au clavier ne montrait plus où l'on était.

   ⛔ ET C'EST LA MÉCANIQUE DU CHAMP, RECONDUITE À L'IDENTIQUE. Bootstrap éteint
   le trait (`.btn:focus-visible`, `.form-check-input:focus` : `outline: 0`, tous
   deux 0,2,0) et le remplace par un halo ; la charte a retiré le halo
   (`--bs-btn-focus-box-shadow: none`, plus haut) en comptant sur
   `:focus-visible` (0,1,0) pour reprendre la main — il ne le pouvait pas.
   ⚠️ RETIRER UN SIGNAL SANS VÉRIFIER QUE L'AUTRE PASSE LAISSE UN CONTRÔLE MUET,
   pas un contrôle sobre : c'est la même leçon que pour `.form-control`, payée
   deux fois le même jour.

   ⚠️ LE POIDS EST CELUI DES RÈGLES CORRIGÉES et l'ORDRE fait le reste, comme
   partout dans cette feuille. `.btn-check` est nommé à part : le contrôle à deux
   positions (« Location » / « Prêt ») cache son `<input>` et ne se voit que par
   son `<label>`, qui est le `.btn` suivant. */
.btn:focus-visible,
.btn-check:focus-visible + .btn,
.form-check-input:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
}

/* ── LA CASE COCHÉE ÉTAIT BLEUE, ET SON FOCUS ÉTAIT UN HALO ────────────────
   ⛔ `.form-check-input:checked` DÉCLARE rgb(13, 110, 253) EN DUR dans
   Bootstrap — pas `--bs-primary`, la valeur elle-même : aucun jeton de la charte
   ne pouvait la corriger. Mesuré sur `?page=config-securite`, huit
   interrupteurs bleus sur l'écran qui décide qui entre dans l'application, et
   la même couleur sur chaque case et chaque radio du produit.

   ⛔ ET SON FOCUS AJOUTAIT UNE OMBRE À LA COULEUR : un halo de quatre pixels en
   rgba(13, 110, 253, .25), déclaré par Bootstrap sur `.form-check-input:focus`
   — la charte n'a pas d'ombre, et ce que le clavier doit montrer est un TRAIT
   (§ ci-dessus).

   ⭐ L'ENCRE, PAS LE ROUGE : une case cochée dit « ceci vaut », pas « attention ».
   Le rouge de la charte reste la couleur de ce qui refuse et de ce qui détruit,
   et le choix à deux positions se peint déjà à l'encre plus haut. */
.form-check-input:checked {
  background-color: var(--acc);
  border-color: var(--acc);
}
.form-check-input:focus {
  border-color: var(--ink-2);
  box-shadow: none;
}

/* ── ET L'INTERRUPTEUR ÉTAIT UNE PASTILLE ──────────────────────────────────
   ⛔ `border-radius: 2em` SUR UNE PISTE DE 2em, C'EST UNE CAPSULE : la seule
   forme arrondie qui restait dans le produit, sur l'écran le plus sérieux qu'il
   ait. La case carrée et l'interrupteur rectangulaire sont la même famille que
   le reste — des filets droits sur du papier.
   ⚠️ LA RADIO GARDE SON ROND, ET C'EST VOULU : `.form-check-input[type=radio]`
   n'est pas nommée ici. Le cercle n'y est pas un coin arrondi, c'est ce qui la
   distingue d'une case à cocher — la retoucher rendrait les deux contrôles
   identiques à l'œil pour deux comportements différents (règle 6).
   ⚠️ ET LE SÉLECTEUR PORTE L'ATTRIBUT, PAS LA SEULE CLASSE : Bootstrap écrit
   `.form-check-input[type=checkbox]` (0,2,0) — une règle sur `.form-check-input`
   seule (0,1,0) passe dessous et ne change rien. Mesuré : elle laissait 3,25 px. */

/* ── UN CHAMP À 14 px, SES VOISINS À 16 ────────────────────────────────────
   ⛔ LE DÉFAUT QUE LE MANDANT A VU À L'ŒIL, DANS SA FORME ORDINAIRE. Le
   14.09.2026, `input-group-lg` donnait un « Ouvrir » géant à côté du champ de
   l’écran d’encaissement rapide ; la classe est partie, et le bouton rendait
   ENCORE 16 px contre 14 au champ collé à lui. Relevé sur les dix écrans :
   `.btn` 16 px, `a.btn` 16 px, `.form-select` 16 px, `<textarea>` 16 px,
   `.input-group-text` 16 px — contre 14 px pour l'`<input>`, 14 px pour
   `.btn-sm` et 14 px pour `.form-select-sm`. Sur `?page=acompte-piece`, une
   seule colonne montrait les deux tailles trois fois : le champ et son « CHF »,
   la liste des modes et le champ client, le bouton d'encaissement.

   ⛔ ET LE LEGACY VOULAIT DÉJÀ 14 px POUR LES TROIS. `css/webbike.css` écrit
   `input…, select, button, textarea { font-size: 14px }` : la première branche
   pèse 0,2,1 et gagne, les trois autres pèsent 0,0,1 et perdent contre
   `.form-select`, `.btn` et `.form-control` (0,1,0). La charte ne choisit donc
   pas une taille neuve — elle rend celle que le produit a toujours voulue.

   ⚠️ PAR LE JETON, COMME LE CHAMP : un écran qui grossit `--taille-champ` sur
   une rangée grossit la rangée entière, et le repli (.875rem) est la taille que
   `.btn-sm` et `.form-select-sm` rendent déjà — la plus répandue des deux.
   ⚠️ ET LE BOUTON PASSE PAR SON JETON À LUI (`--bs-btn-font-size`), sans quoi
   `.btn-sm`, qui le redéclare, resterait maître de la moitié des boutons. */
.btn { --bs-btn-font-size: var(--taille-champ, .875rem); }
.form-select,
.input-group-text,
textarea.form-control {
  font-size: var(--taille-champ, .875rem);
}

/* ── UNE LISTE DE LA BARRE D'OUTILS PRENAIT TOUTE LA LARGEUR DE LA PAGE ────
   ⛔ MESURÉ SUR `?page=caisse-cloture` À 1 366 px : le `<select>` « Caisse »
   rendait 1 334 px de large, seul sur une deuxième ligne, sous son propre
   libellé resté en haut. `.form-select` est `width: 100%` ; dans une rangée
   flex, ce 100 % se résout sur la BARRE, le contrôle réclame toute la page, ne
   tient pas, et passe à la ligne en s'y étalant.

   ⚠️ LES ÉCRANS S'EN SORTAIENT PAR UNE LARGEUR EN DUR, un par un
   (`style="width:14rem"`) — celui qui l'oubliait cassait, sans que rien le dise.
   Un critère se dimensionne sur ce qu'il montre : `width: auto`, et une largeur
   déclarée par un écran (style en ligne) reste maîtresse. */
.wb-outils .form-control,
.wb-outils .form-select { width: auto; }

/* ── LES ONGLETS DE PORTÉE D'UNE GRILLE ÉTAIENT UN COMMUTATEUR À PASTILLES ──
   ⛔ TROISIÈME BARRE DE POSITION DU PRODUIT, ET LA SEULE À NE PAS EN AVOIR LA
   FORME. « En stock / Parc location / Sortis / Tous » (`.wb-dt-scope-tabs`,
   `css/app.css`, rendu par `App\Datatable`) était une boîte à coins de 8 px
   contenant des pavés de 6 px, dont l'actif portait un fond et une
   `box-shadow` — le seul coin arrondi et la seule ombre de l'écran, deux
   centimètres sous deux barres qui disent « vous êtes ici » par un filet.

   ⭐ MÊME NATURE, MÊME COMPOSANT (règle 6) : la géométrie est celle de
   `.wb-onglet` — filet de 2 px sous l'entrée ouverte, recouvrant le filet du
   conteneur —, et sa couleur est LE rouge, comme la sous-navigation et comme
   les onglets de sous-section depuis le 14.09.2026.
   ⚠️ LE CONTENEUR PASSE EN `flex` : en `inline-flex`, son filet s'arrêtait au
   dernier onglet, c'est-à-dire en plein milieu de la page — le défaut que la
   revue précédente a corrigé sur la fiche client. */
.wb-dt-scope-tabs {
  display: flex;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 0;
  gap: .15rem;
  margin-bottom: 1rem;
}
.wb-dt-scope-tabs button {
  border-radius: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: .4rem .75rem .5rem;
  font-size: .87rem;
}
.wb-dt-scope-tabs button.wb-dt-scope-active {
  background: transparent;
  box-shadow: none;
  border-bottom-color: var(--acc);
  color: var(--ink);
}

/* ── ET LE QUATRIÈME JEU D'ONGLETS ÉTAIT UN DOSSIER À LANGUETTE ────────────
   ⛔ QUATRE BARRES DE POSITION, QUATRE FORMES. Sur `?page=config-securite`, la
   rangée « Réglages / Réseaux / Pays / Comptes / Appareils / Journal »
   (`.nav-tabs` de Bootstrap) est rendue DIRECTEMENT SOUS la rangée des onglets
   de sous-section, qui souligne — et elle, elle encadre : trois bordures et un
   fond, la languette d'un dossier à intercalaires. Deux barres empilées, deux
   formes, la même question posée deux fois.

   ⭐ MÊME GÉOMÉTRIE QUE LES TROIS AUTRES : plus que le filet du bas, 2 px sous
   l'onglet ouvert, en rouge, recouvrant le filet du conteneur.
   ⚠️ TOUT PASSE PAR LES JETONS DE BOOTSTRAP, sauf la largeur de bordure : la
   couleur d'un `border-color` à trois valeurs est ce que `--bs-nav-tabs-link-
   active-border-color` attend, et c'est la seule façon de n'allumer que le bas
   sans écrire un sélecteur plus lourd que celui du composant. */
.nav-tabs {
  --bs-nav-tabs-border-color: var(--rule);
  --bs-nav-tabs-link-active-bg: transparent;
  --bs-nav-tabs-link-active-color: var(--ink);
  --bs-nav-tabs-link-active-border-color: transparent transparent var(--acc);
  --bs-nav-tabs-link-hover-border-color: transparent;
  --bs-nav-link-color: var(--ink-3);
  --bs-nav-link-hover-color: var(--ink);
}
.nav-tabs .nav-link { border-width: 0 0 2px; }
.nav-tabs .nav-link.active { font-weight: 600; }

/* ══ 14.09.2026 — CE QUE LA QUATRIÈME REVUE A VU (lot CS) ═══════════════════
   Les six grilles du comptoir rendues AVEC LEURS LIGNES — véhicules, articles,
   pipeline pièces, clients, facturation, commandes —, trente-deux lignes de
   données plausibles chacune, à 1 366, 1 920 et 2 560 px, parcourues à la
   touche Tab. Les trois revues précédentes n'avaient vu d'une grille que son
   en-tête, sa barre d'outils et son état vide : Tabulator vient du CDN et la
   donnée d'une API.

   ⛔ ET CE QUI EST APPARU N'ÉTAIT PAS DANS LA CHARTE DU TOUT. `css/app.css`
   habille les grilles, mais il se charge AVANT `tabulator_bootstrap5.min.css`
   (`index.php`) : à spécificité égale, c'est le thème qui gagne. La moitié du
   bloc « densité » d'app.css ne rendait donc rien, et ce que l'écran montrait
   était le thème Bootstrap de Tabulator — sa fonte, ses coins, ses halos, son
   bleu. Ce § est écrit depuis la fin de la cascade, avec le poids de ce qu'il
   corrige. */

/* ── LA GRILLE ÉCRIVAIT PLUS GROS QUE LA PAGE, ET C'EST L'ÉCRAN LE PLUS DENSE
   ⛔ MESURÉ : `.tabulator` rendait 16 px de fonte, contre 13 px au `body` et
   10,88 px à l'en-tête de ses propres colonnes. Le thème déclare
   `.tabulator { font-size: 16px }` et `css/app.css` demandait `0.8rem` au même
   poids, plus haut dans la cascade : la demande du produit était simplement
   perdue. Un tableau qui écrit plus gros que le texte qui l'annonce se lit
   comme une capture d'écran collée dans la page.
   ⭐ IL SUIT DÉSORMAIS SA PAGE (`inherit`) : un écran qui change d'échelle
   emmène sa grille, et la charte n'invente pas une quatrième taille.
   ⭐ ET SES CHIFFRES SONT TABULAIRES : une colonne de montants, de kilomètres
   ou de quantités se lit en COLONNE — sans chasse fixe, les unités ne tombent
   pas les unes sous les autres et l'œil ne peut plus comparer. */
.tabulator {
  font-size: inherit;
  font-variant-numeric: tabular-nums;
}

/* ⛔ ET LE CADRE DE LA GRILLE AVAIT DES COINS DE 8 px (`css/app.css`, 0,2,0) —
   le dernier arrondi des écrans de liste, sur le plus grand objet de la page.
   La charte se tient au trait : un filet droit sur du papier. */
.tabulator.tabulator {
  border-radius: var(--bs-border-radius-lg);
  border-color: var(--rule);
}

/* ── L'EN-TÊTE DE COLONNE ÉTAIT UNE QUATRIÈME FORME DE LIBELLÉ ─────────────
   ⛔ 10,88 px / 700 / interlettrage .03em, quand `.form-label`, `.lbl`,
   `.kv > dt` ET `.table > thead th` écrivent tous 9,5 px / 600 / .09em en
   `--ink-3`. Même nature, même composant (règle 6) : le titre d'une colonne de
   grille EST le libellé réglé d'une colonne de tableau, et le produit en avait
   deux écritures pour un rôle.
   ⚠️ LE SÉLECTEUR EST CELUI D'`app.css` (0,5,0), sans quoi rien ne change. */
.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title {
  font-size: 12.5px;
  font-weight: 600;

  color: var(--ink-3);
}

/* ⛔ ET LE FILET SOUS LA TÊTE EST D'ENCRE, comme sous celle d'un `.table` : il
   sépare ce qu'on lit de ce qui le nomme, et un filet clair de plus au milieu
   de vingt filets clairs ne séparait rien. */
.tabulator .tabulator-header {
  border-bottom: 1px solid var(--rule-2);
}

/* ⛔ ET LA MARGE INTERNE DE L'EN-TÊTE MANGEAIT LES TITRES. Le thème pose 12 px
   sur les quatre côtés là où `css/app.css` demande 4 px 6 px depuis toujours —
   même sélecteur, chargé plus tôt, donc perdu. Mesuré : 24 px de marge plus
   12 px de chevron de tri sur une colonne de 90 px ne laissaient que 54 px de
   libellé, et « N° cmd », « Reste », « Âge (j) » et « Commandée le » sortaient
   écrêtés — un titre de colonne coupé ne dit plus ce que la colonne contient.
   ⚠️ LA CELLULE, ELLE, A DÉJÀ LA SIENNE : `css/app.css` la pose avec un
   sélecteur à cinq classes, qui passe devant le thème. */
.tabulator .tabulator-header .tabulator-col .tabulator-col-content {
  padding: 4px 6px;
}

/* ── UN FILTRE DE COLONNE SE DÉGUISAIT EN CHAMP BOOTSTRAP ──────────────────
   ⛔ MESURÉ SUR LES SIX GRILLES : le champ de filtre d'en-tête rendait 16 px de
   fonte et 34 px de haut, avec un coin de 4 px — c'est le thème Tabulator qui
   le peint en `.form-control` (0,4,0, chargé après `css/app.css`, qui lui
   demandait 0,75rem). À côté, les deux champs d'une plage (« Min » / « Max »),
   posés par `js/datatable.js`, rendaient 12,8 px et 26 px : DEUX tailles de
   filtre dans la MÊME ligne d'en-tête, sur `?page=articles-liste`.
   ⭐ UN FILTRE EST UN CHAMP DE LA GRILLE : il prend la fonte de la grille et la
   hauteur qu'il faut pour la porter, sans coin — et il en tient DEUX dans une
   colonne de 70 px, ce que 34 px de haut et 12 px de marge interdisaient.
   ⛔ ET SON FOCUS ÉTAIT BLEU (`border-color: #86b7fe`), sur l'écran qu'on
   parcourt le plus au clavier — la charte ne connaît pas ce bleu. */
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input,
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter select {
  font-size: inherit;
  line-height: 1.3;
  padding: 2px 4px;
  border: 1px solid var(--rule-2);
  border-radius: var(--bs-border-radius-sm);
  color: var(--ink);
  background-color: var(--paper);
}
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input:focus,
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter select:focus {
  border-color: var(--ink-2);
  box-shadow: none;
}
/* ⛔ ET IL N'AVAIT AUCUN TRAIT DE FOCUS. Bootstrap et le thème éteignent
   l'`outline` à 0,4,0 sur ce champ-là : parcouru à la touche Tab, il ne
   montrait rien — c'est la leçon du 14.09.2026, payée une troisième fois. */
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input:focus-visible,
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter select:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
}

/* ── UN LIEN DE CELLULE ÉTAIT SOULIGNÉ, L'AUTRE PAS ───────────────────────
   ⛔ `.wb-dt-lien-page` (le lien vers une VRAIE page) portait un soulignement
   et `.wb-entity-link` (l'aperçu) n'en portait pas : deux liens de même nature,
   deux apparences (règle 6). Et c'est le soulignement qui a tort — la charte ne
   souligne un lien QUE dans le texte courant ; dans une grille,
   il se reconnaît à sa colonne.
   ⚠️ LES DEUX PORTENT LE MÊME CURSEUR, qui est ce qui les annonce. */
.tabulator .tabulator-row .wb-dt-lien-page,
.tabulator .tabulator-row .wb-entity-link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

/* ⛔ ET LES BOUTONS DE LIGNE AVAIENT DES COINS DE 6 px (`css/app.css`, 0,4,0) :
   quatre à six pastilles par ligne, sur trente lignes. */
.tabulator .tabulator-row .wb-dt-actions-cell .btn {
  border-radius: var(--bs-border-radius-sm);
}

/* ── LE PIED DE GRILLE ÉTAIT UNE BARRE GRISE À BOUTONS ARRONDIS ────────────
   ⛔ MESURÉ : fond #e6e6e6, texte #555, graisse 700, « Lignes/page » à 16 px à
   côté d'une liste à 14, des boutons de page de 39 px de haut à coins de 3 px
   sur un fond blanc translucide, et un survol qui peignait le chiffre en BLANC
   sur gris foncé. Rien de tout cela n'est de la charte : c'est le thème.
   ⭐ LE PIED EST UNE BARRE DE LA PAGE : papier, filet, encre, une seule taille.
   La page ouverte est d'encre pleine — c'est la position qui vaut, comme
   partout ailleurs (`.btn-check:checked`, l'onglet ouvert). */
.tabulator .tabulator-footer {
  background: var(--panel);
  border-top: 1px solid var(--rule);
  color: var(--ink);
  font-weight: 400;
  font-size: inherit;
}
.tabulator .tabulator-footer label,
.tabulator .tabulator-footer .tabulator-page-counter {
  font-size: inherit;
  font-weight: 400;
}
/* ⚠️ ET LES DEUX BOUTONS DU BOUT SONT NOMMÉS PAR LEUR ATTRIBUT : le thème
   arrondit « << » et « >> » par `[data-page=first]` / `[data-page=last]`
   (0,3,1) — une règle de classe seule leur laissait leurs coins, et c'est
   exactement ce que la mesure a montré au premier jet de ce §. */
.tabulator .tabulator-footer .tabulator-page-size,
.tabulator .tabulator-footer .tabulator-page,
.tabulator .tabulator-footer .tabulator-page[data-page=first],
.tabulator .tabulator-footer .tabulator-page[data-page=last] {
  border: 1px solid var(--rule-2);
  border-radius: var(--bs-border-radius-sm);
  background: var(--paper);
  color: var(--ink);
  font-size: inherit;
  padding: 2px 8px;
  margin: 0 2px;
}
.tabulator .tabulator-footer .tabulator-page:not(disabled):hover {
  background: var(--wash);
  color: var(--ink);
}
.tabulator .tabulator-footer .tabulator-page.active,
.tabulator .tabulator-footer .tabulator-page.active:not(disabled):hover {
  background: var(--acc);
  border-color: var(--acc);
  color: #fff;
}
.tabulator .tabulator-footer .tabulator-page:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
}

/* ── « IL N'Y A RIEN À MONTRER » ÉTAIT ÉCRIT EN 20 px GRIS CLAIR ───────────
   ⛔ Le thème rend l'état vide en `#ccc`, 20 px, graisse 700 — le plus gros
   texte de l'écran pour dire qu'il n'y a rien, et dans la seule couleur que la
   charte réserve à ce qui est illisible. L'état vide du produit est UNE PHRASE
   (`wbEtat.vide`) : texte courant, encre secondaire. */
.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents {
  color: var(--ink-3);
  font-size: inherit;
  font-weight: 400;
}

/* ── LA BARRE DE CRITÈRES ÉTAIT UN CHAPELET DE PASTILLES ───────────────────
   ⛔ `css/app.css` lui donnait un cadre de 8 px, des puces de 14 px — donc des
   capsules — et une croix de fermeture ronde dont le survol virait au rouge
   Bootstrap `#f8d7da` / `#58151c`, deux couleurs en dur qu'aucun jeton
   n'atteint. C'est la seule barre du produit qui portait encore des pastilles.
   ⭐ CE QUI RESTE : un filet, du papier, et le rouge de la charte pour ce qui
   RETIRE — la croix efface un critère, c'est le geste qui détruit. */
.wb-dt-criteres,
.wb-dt-critere,
.wb-dt-critere-x {
  border-radius: var(--coin-s);
}
.wb-dt-critere-x:hover {
  background: var(--red-w);
  color: var(--red);
}

/* ── « J'ATTENDS LA DONNÉE » ÉTAIT UNE BOÎTE NOIRE SUR UN VOILE SOMBRE ─────
   ⛔ MESURÉ À CHAQUE CHARGEMENT, DE CHAQUE GRILLE : le thème couvre la table
   d'un voile `rgba(0, 0, 0, .4)` et y pose une boîte blanche à coins de 10 px,
   cernée d'un trait noir de 4 px, en 16 px gras — le seul objet du produit qui
   assombrit l'écran, et il apparaît à chaque page, chaque tri, chaque filtre.
   ⭐ CE QUE LA CHARTE DIT DE CETTE ATTENTE : une PHRASE, jamais une animation
   ni une boîte (`wbEtat.chargement`). Le voile devient donc du
   papier — ce qu'il couvre est périmé, on ne le donne plus à lire — et la
   phrase s'y écrit en encre secondaire, à la taille de la grille. */
.tabulator .tabulator-alert {
  background: var(--paper);
}
.tabulator .tabulator-alert .tabulator-alert-msg,
.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg {
  background: transparent;
  border: 0;
  border-radius: var(--coin);
  font-size: inherit;
  font-weight: 400;
  color: var(--ink-3);
}
/* ⚠️ ET L'ÉCHEC GARDE LE ROUGE DE LA CHARTE, qui est la couleur de ce qui
   refuse — le thème y met un `#d00` et un `#590000` écrits en dur. */
.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error {
  border: 0;
  color: var(--red);
}

/* ── LES HUIT GRILLES RESTANTES, RENDUES AVEC LEURS LIGNES (14.09.2026) ─────
   Même méthode, huit jeux de plus : passages à l'atelier, pièces non vendues,
   contrats de location, opt-outs, plans d'entretien, sessions et secteurs
   d'inventaire, collaborateurs — trente-deux lignes plausibles chacune, à
   1 366, 1 920 et 2 560 px. Ce que ces huit-là ont montré et que les six
   premières ne pouvaient pas montrer : une grille peut être ÉTROITE. */

/* ⛔ UNE GRILLE ÉTROITE S'ÉTIRAIT À LA LARGEUR DE LA PAGE, ET C'EST LE REVERS
   EXACT DU DÉFAUT PRÉCÉDENT. `layout: 'fitDataFill'` remplit le cadre jusqu'à
   son bord : c'est ce qu'on voulait pour une grille de treize colonnes, qui
   sans lui laissait « un rectangle vide bordé à droite ». Mais
   `?page=config-services` n'a que 923 px de colonnes et `?page=collaborateurs`
   867 — sur un poste 4K (2 528 px de page), la table posait donc 1 600 px de
   LIGNES RÉGLÉES VIDES à droite de la dernière colonne, pied de pagination
   compris. Vingt lignes bordées qui ne contiennent rien se lisent « il y a des
   colonnes par là », et l'œil part les chercher.
   ⭐ LA GRILLE S'ARRÊTE OÙ S'ARRÊTENT SES COLONNES. `fit-content` laisse le
   cadre épouser la table ; `max-width: 100%` garde le défilement horizontal des
   grilles LARGES à 1 366 px (mesuré : 1 665 px de colonnes défilent dans
   1 332 px de cadre, et la page ne déborde pas). Les deux choix globaux de la
   revue précédente restent donc vrais tels quels : `fitDataFill` remplit quand
   il y a de quoi remplir, `pleineLargeur` affranchit du plafond sans étirer. */
.tabulator.tabulator {
  width: fit-content;
  max-width: 100%;
}

/* ⛔ … SAUF LA GRILLE `fitColumns` (23.09.2026, lot RVA), dont les colonnes se
   PARTAGENT la largeur du cadre : un cadre qui épouse ses colonnes n'a rien à
   partager, et les colonnes extensibles tombaient à leur minimum. Mesuré sur
   `?page=rapport-ventes-articles` à 2 000 px : 762 px de grille dans 1 948 de
   page, « Désignation », « Catégorie » et « Groupe » à 40 px chacune.
   Tabulator pose `tabulator-layout` sur son élément : la règle ne vise que ce
   mode-là. */
.tabulator.tabulator[tabulator-layout="fitColumns"] {
  width: auto;
  max-width: min(100%, 1800px);
}

/* Sur l'encre, un filet clair sépare Filtres de son ✕. */
.wb-dt-filtres > .btn-primary + .btn-primary {
  border-left-color: color-mix(in srgb, var(--paper) 45%, transparent);
}
/* Filtres garde ses coins tant que son ✕ est masqué. */
.wb-dt-filtres > .btn:has(+ .btn.d-none) {
  border-top-right-radius: var(--bs-btn-border-radius);
  border-bottom-right-radius: var(--bs-btn-border-radius);
}

/* Le contenu des cellules est centré verticalement : la ligne s'étire sur sa
   hauteur minimale (40 px) et chaque cellule la suit. `align-content` garde
   l'ellipse, que `display: flex` perdrait. Une ligne à cellule sur deux lignes
   reste alignée en haut. */
.tabulator .tabulator-row:not(.tabulator-group) {
  display: flex;
}
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell {
  flex: none;
  height: auto !important;
  align-content: center;
}
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row:has(.wb-vehicule-cell) .tabulator-cell {
  align-content: start;
}
.tabulator .tabulator-row .wb-dt-actions-cell {
  align-items: center;
}
/* L'en-tête figé couvre les titres qui défilent dessous (transparent, il les montrait). */
.tabulator .tabulator-header .tabulator-col.tabulator-frozen {
  background: var(--wb-dt-surface-alt);
}

/* ⛔ LE CORPS DÉFILANT D'UNE GRILLE EST UN ARRÊT DE TABULATION SANS TRAIT.
   Chromium rend focalisable tout conteneur qui défile — c'est ce qui permet de
   parcourir aux flèches une grille plus large que l'écran, et il faut le
   garder. Mais parcouru à la touche Tab, il ne montrait RIEN : entre le dernier
   filtre d'en-tête et la première ligne, le focus disparaissait un coup. C'est
   la leçon du filtre d'en-tête, payée une fois de plus.
   ⚠️ TRAIT RENTRANT (`-1px`) : posé dehors, il doublerait le filet du cadre. */
.tabulator .tabulator-tableholder:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: -1px;
}

/* ⛔ LA PLAQUE D'UN CONTRAT ÉTAIT PEINTE EN BOOTSTRAP. `css/webbike.css` écrit
   `#6c757d` sur `.wb-vehicule-plaque` et `#997404` sur les deux repères de
   plaque reconstituée — le gris « secondary » et l'ambre « warning-emphasis »
   du cadre, deux couleurs en dur qu'aucun jeton n'atteint, sur la seule cellule
   à deux lignes du produit (`?page=locations`, colonne « Véhicule »).
   ⭐ LA PLAQUE EST UNE MENTION SECONDAIRE — `--ink-3`, comme toute glose ; et le
   repère « cette plaque n'est pas celle du papier » est un ÉTAT DE CONTRÔLE, pas
   une teinte choisie : `--etat-attention`, le même que partout ailleurs. */
.wb-vehicule-plaque,
.wb-plaque-non-gelee {
  color: var(--ink-3);
}
.wb-plaque-changee,
.wb-plaque-non-gelee-icone {
  color: var(--etat-attention);
}
/* ══ 14.09.2026 — CE QUE LA SIXIÈME REVUE A VU (lot CU) ═══════════════════
   Les CALENDRIERS et le PLANNING rendus et regardés : le planning d'atelier
   (« Le Bac », ses deux modes), les livraisons (mois, semaine, jour), les
   locations (grille maison) et le planning des absences, avec vingt à
   soixante-quatre événements plausibles chacun — noms composés, raisons
   sociales avec « & », accents, textes longs, chevauchements —, à 1 366, 1 920
   et 2 560 px, au `getComputedStyle` et à la touche Tab.

   ⛔ ET LE MOTEUR N'AVAIT JAMAIS ÉTÉ HABILLÉ. `js/event-calendar-5.12.0/` est
   VENDORISÉ : il porte sa propre planche — un bleu `oklch(70.7% .165 254.624)`
   pour l'événement, des coins de 3 px, une ombre d'un pixel sur chaque
   pastille, un bouton à coins de 4 px. Trois écrans le chargent, et chacun
   l'avait retouché DANS SA PROPRE FEUILLE, avec ses valeurs : les livraisons en
   'Segoe UI' et douze couleurs Material, les absences en palette Bootstrap, les
   locations en cinq teintes de `bg-*`. Quatre planches pour un composant.

   ⭐ CE § HABILLE LE MOTEUR PAR SES PROPRES JETONS. event-calendar expose tout
   son décor en variables sur `.ec` : les redéfinir depuis la charte vaut mieux
   que de lutter avec ses sélecteurs, et cela tient pour les vues qu'aucun écran
   ne montre encore. Ce qui n'est pas une variable chez lui — coin, ombre,
   échelle de fonte — est repris ci-dessous, nommément. */

/* ── LES JETONS DU MOTEUR, REPRIS UN PAR UN ────────────────────────────────
   ⚠️ SUR `.ec`, LÀ OÙ LE BUNDLE LES POSE : une redéfinition sur `:root` serait
   battue par la sienne, qui est plus précise.
   ⛔ ET LE SÉLECTEUR EST DOUBLÉ (`.ec.ec`), C'EST LA LEÇON DE LA QUATRIÈME
   REVUE : la feuille du bundle est chargée par la PAGE, donc APRÈS
   `css/registre.css` — à spécificité égale, c'est elle qui gagnait, et les
   pastilles rendaient encore le bleu par défaut au premier jet de ce §. Un
   poids de deux classes suffit ; les écrans, eux, sont chargés après et restent
   maîtres de leurs propres teintes. */
.ec.ec {
  --ec-bg-color: var(--panel);
  --ec-text-color: var(--ink);
  --ec-border-color: var(--rule);

  /* ⭐ « AUJOURD'HUI » ET LE JOUR REMARQUABLE SONT LES REPÈRES DE LA CHARTE,
     ceux que le planning des absences pose déjà (`--repere-*`, § du 13.09.2026).
     Le bundle en avait deux autres, en oklch. */
  --ec-today-bg-color: var(--repere-aujourdhui-w);
  --ec-highlight-color: var(--repere-ferie-w);

  /* ⛔ L'ÉVÉNEMENT SANS COULEUR EST UN BLOC DE REGISTRE, PAS UN APLAT BLEU :
     le lavis du retrait, l'encre pour le mot — la forme qu'a déjà une barre de
     location (`css/location_calendrier.css`) et une note de la charte. Le bleu
     `oklch(70.7% .165 254.624)` du bundle, lui, n'est dans aucune planche de la
     maison, et c'est ce qu'on voyait sur quarante pastilles du mois. */
  --ec-event-bg-color: var(--wash);
  --ec-event-text-color: var(--ink);
  --ec-bg-event-color: var(--rule-2);

  /* La ligne « maintenant » est LE rouge, comme tout ce qui alerte. */
  --ec-now-indicator-color: var(--red);

  --ec-button-bg-color: var(--panel);
  --ec-button-border-color: var(--rule-2);
  --ec-button-text-color: var(--ink);
  --ec-button-active-bg-color: var(--ink);
  --ec-button-active-border-color: var(--ink);
  --ec-button-active-text-color: var(--paper);
  --ec-popup-bg-color: var(--panel);
}

/* ⛔ NI COIN, NI OMBRE SUR UNE PASTILLE D'ÉVÉNEMENT. Mesuré sur les trois
   écrans : un rayon de 3 px et une ombre d'un pixel viennent du bundle,
   4 px de plus venaient de `css/livraison_calendrier.css` — sur quarante
   pastilles à l'écran, c'est quarante fois le décor que le Lot 6 a fermé.
   ⚠️ ET LA TAILLE SUIT LA PAGE : le bundle écrit `font-size: .85em` sur la
   pastille et `1rem` sur son titre, donc deux échelles dans le même rectangle,
   et aucune des deux n'est celle du texte qui l'annonce. */
.ec .ec-event,
.ec .ec-event.ec-start-clipped,
.ec .ec-event.ec-end-clipped,
.ec .ec-event-tag,
.ec .ec-button {
  border-radius: var(--coin);
}
.ec .ec-event {
  box-shadow: none;
  font-size: inherit;
  font-family: var(--fonte);
}
.ec .ec-event-title,
.ec .ec-event-time {
  font-size: inherit;
}

/* ── LA GRILLE DU MOIS EST CELLE DU MOIS D'ATELIER (23.09.2026, lot LIVCAL) ─
   ⭐ Le composant le plus proche d'un calendrier du moteur n'est pas
   `.form-label` (le libellé d'un champ, 9,5 px Archivo, auquel ce § alignait
   l'en-tête de jour), c'est `.wb-mo` d'`app/pages/atelier-mois.php` : une
   grille de jours. L'en-tête de colonne prend donc la forme de `.wb-mo th`, le
   quantième celle de `.wb-mo-n`, le mois voisin celle de `.wb-mo-c-hors`
   (règle 6).
   ⚠️ CE QUI EST POSÉ ICI VAUT POUR LES TROIS CALENDRIERS DU MOTEUR —
   livraisons, absences des collaborateurs, locations —, et c'est voulu. */
.ec .ec-col-head,
.ec .ec-col-head > * {
  font-family: var(--fonte-mono);
  font-size: 12px;
  font-weight: 600;


  text-align: left;
  color: var(--ink-2);
}
.ec.ec.ec-day-grid.ec-month-view { --ec-bg-color: var(--paper); }

/* ⚠️ LE QUANTIÈME EST UNE DONNÉE, PAS UN LIBELLÉ : monospacé, encre pleine, en
   haut à GAUCHE — le moteur le pose à droite (`row-reverse`). Le passé
   (`.wb-cal-passe`) et aujourd'hui sont dans `css/app.css`, § Calendriers. */
.ec .ec-day-head,
.ec .ec-day-head time {
  font-family: var(--fonte-mono);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ec.ec-day-grid.ec-month-view .ec-day-head {
  flex-direction: row;
  padding: .3rem .4rem 0;
}
.ec .ec-day.ec-other-month { --ec-day-bg-color: var(--wash); }
.ec .ec-day.ec-other-month .ec-day-head time {
  opacity: 1;
  color: var(--ink-3);
}

/* « + N livraisons » (`moreLinkContent`) se lit comme le compte d'heures d'une
   case du mois d'atelier (`.wb-mo-h`) ; son volet n'a ni coin ni ombre. */
.ec .ec-day-foot {
  font-family: var(--fonte-mono);
  font-size: 12px;
  text-align: right;
  padding: .2rem .4rem;
}
.ec .ec-day-foot,
.ec .ec-day-foot a { color: var(--ink-3); }
.ec.ec .ec-popup {
  border-radius: var(--bs-border-radius-lg);
  box-shadow: none;
  border-color: var(--rule-2);
}

/* ── LES DEUX LIGNES D'UNE LIVRAISON ─────────────────────────────────────
   Client et heure, puis véhicule et collaborateur (`.lc-ev`, `js/livraisons.js`). */
.wb-fj-l1 { display: flex; justify-content: space-between; gap: .4rem;
  align-items: baseline; min-width: 0; }
.wb-fj-cli { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wb-fj-h { font-family: var(--fonte-mono); font-size: 12px; color: var(--ink-2);
  white-space: nowrap; font-variant-numeric: tabular-nums; }
.wb-fj-l2 { display: flex; justify-content: space-between; gap: .4rem; min-width: 0;
  font-family: var(--fonte-mono); font-size: 12px; color: var(--ink-2); }
.wb-fj-veh { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wb-fj-quand { color: var(--ink-3); white-space: nowrap; }

/* ── L'HEURE DE LA MARGE EST UN CHIFFRE, DONC MONOSPACÉE ───────────────────
   Une colonne d'heures se lit en COLONNE : sans chasse fixe, « 8h » et « 10h »
   ne s'alignent pas, et l'œil ne peut plus suivre l'échelle. */
.ec .ec-sidebar .ec-slot,
.ec .ec-slots .ec-slot,
.ec .ec-time {
  font-family: var(--fonte-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* ⛔ ET UNE TOUCHE DE CLAVIER N'A PAS DE COIN. `<kbd>` est arrondi à .25rem par
   Bootstrap — en dur, hors de `--bs-border-radius` —, `.wb-kbd` et `.wb-hint`
   (`css/app.css`) le recopiaient chacun avec sa valeur : trois rayons pour un
   rôle. */
kbd,
.wb-kbd,
.wb-hint {
  border-radius: var(--coin-s);
}

/* Une touche qui suit le libellé d'un lien ou d'un bouton : un écart fixe, ici seulement.
   L'espace du balisage ne compte pas (il se perd en flex et s'ajoutait aux marges). */
:is(a, button) > :is(kbd, .wb-hint) { margin-left: 6px; }

/* ── LA TEINTE D'UN TYPE D'ABSENCE N'A PLUS QU'UN SEUL AUTEUR ─────────────
   ⭐ ELLE LIT LES ENCRES D'ÉTAT DE CE FICHIER (15.09.2026, décision du mandant),
   une par type et selon son SENS — la correspondance est écrite en tête de
   `css/collaborateurs.css`, qui reste le seul endroit où elle se décide. Ce §
   disait l'inverse : quatre valeurs qui distinguent des TYPES entre eux ne
   seraient pas une échelle nommée. Un type d'absence porte pourtant un sens —
   à arbitrer, qui coûte, rien à dire, conforme —, et c'est exactement ce que
   nomment `--etat-attention`, `--etat-ecart`, `--etat-neutre` et `--etat-ok`.
   ⛔ ELLES ÉTAIENT AUSSI ÉCRITES DEUX FOIS, et la seconde était un SERVEUR :
   `app/actions/collaborateurs-calendrier-data.php` posait `#0d6efd`, `#d9480f`,
   `#198754`, `#6f42c1` sur chaque événement en `color` — donc en style EN LIGNE
   sur la pastille, hors d'atteinte de toute feuille. Le lot CU les y retire :
   la pastille porte sa classe `.wb-abs-type-N`, comme le carré de la légende, et
   la feuille reste seule à décider d'une couleur. */

/* ══ 14.09.2026 — CE QUE LA SEPTIÈME REVUE A VU (lot CW) ═══════════════════
   Les VINGT-SIX ÉCRANS DE RÉGLAGES et « Mon profil », rendus et regardés l'un
   après l'autre à 1 366, 1 920 et 2 560 px, au `getComputedStyle` et à la
   touche Tab — réglages remplis, listes de cinq à trente lignes, textes longs,
   une valeur vide, un « & », des accents. C'est la FAMILLE la plus nombreuse du
   produit, et la seule où l'on ne fait QUE remplir un formulaire : deux écrans
   voisins qui ne posent pas leur bouton au même endroit se voient tout de
   suite. */

/* ── LA DERNIÈRE FEUILLE ÉLEVÉE DU PRODUIT ─────────────────────────────────
   ⛔ `.wb-card` (`css/app.css`) PORTE UN COIN DE 8 px ET UNE OMBRE DE DOUZE,
   ÉCRITS EN DUR. C'est la carte de la MOSAÏQUE D'ACCUEIL, morte avec le Lot 7 :
   sa feuille l'a dit le 24.08.2026 — « les écrans de configuration s'en servent
   encore, ils migreront avec leur planche » —, et deux ans de charte plus tard
   ils s'en servent toujours. Mesuré : douze rectangles sur trois écrans
   (`?page=config-dashboard`, `?page=config-decodeur-vin`,
   `?page=config-email-verification`) rendaient `border-radius: 8px` et
   `box-shadow: rgba(0,0,0,.06) 0 2px 12px`, les seuls coins et les seules
   élévations de toute la famille — les vingt-trois autres écrans posent la
   `.card` de Bootstrap, que la charte tient depuis le § « Feuilles posées ».

   ⭐ ELLE REJOINT DONC CETTE FAMILLE, PAR SON NOM : un filet, jamais une
   élévation. Le fond et le filet passent aux jetons du même geste — `.wb-card`
   lisait `--bs-body-bg` et `--bs-border-color`, c'est-à-dire le papier de la
   PAGE pour une feuille POSÉE dessus.
   ⚠️ CE § NE TOUCHE PAS À SA GÉOMÉTRIE (`display: flex`, `height: 100%`,
   `padding: 18px`) : trois écrans s'appuient dessus, et ce n'est pas la charte
   qui décide d'une mise en page. Ce qu'ils n'ont pas, c'est le bandeau de titre
   d'une `.card` — leur aligner le BALISAGE reste un arbitrage. */
.wb-card {
  background: var(--panel);
  border-color: var(--rule-2);
  border-radius: var(--bs-border-radius-lg);
  box-shadow: none;
}

/* ── UNE ENCRE SÉMANTIQUE NE DÉPEND PAS DE LA PROPRIÉTÉ QUI LA LIT ─────────
   ⛔ LE LOT BW A REPEINT LE TEXTE ET OUBLIÉ LE CADRE. Le § du 14.09.2026
   (« les encres sémantiques ») a donné une classe de charte aux cent
   quatre-vingt-quatorze `text-success` / `text-warning` du produit, en notant
   que `text-danger`, lui, allait déjà — parce que `--bs-danger` est remis au
   rouge du Registre juste sous la planche de jetons. La leçon n'a pas été tirée
   jusqu'au bout : `--bs-warning-rgb` et `--bs-success-rgb`, eux, sont restés
   ceux de Bootstrap, et TOUTE propriété qui les lit rendait hors charte.
   Mesuré sur `?page=config-taches-planifiees` : le cadre de la tâche qui
   SUPPRIME DES DONNÉES rendait rgb(255, 193, 7) — l'ambre de Bootstrap,
   illisible sur le papier de la charte, quand `--etat-attention` vaut #A8791F.
   Et sur `?page=config-email-verification`, la barre de quota rendait le VERT
   de Bootstrap (#198754) quand `--etat-ok` vaut #4A7C59.

   ⭐ DEUX LIGNES AU MÊME ENDROIT QUE LE ROUGE, ET LE COMPTE EST CLOS : un cadre
   (`border-warning`, `border-success`), un fond (`bg-success` d'une
   `progress-bar`), un `-subtle` — tout lit ces deux jetons, et plus rien n'a à
   être repeint composant par composant.
   ⚠️ LES CLASSES DE TEXTE RESTENT INTERDITES (`HarmonieBootstrapTest`), et ce
   n'est pas contradictoire : un RÔLE n'a qu'une écriture, et l'encre du texte a
   déjà la sienne (`.wb-encre-ok`, `.wb-encre-att`). Ces jetons-ci tiennent ce
   qu'AUCUNE classe de charte ne dit — le cadre et le remplissage. */
:root {
  --bs-warning: var(--etat-attention);
  --bs-warning-rgb: 168, 121, 31;
  --bs-warning-border-subtle: var(--etat-attention);
  --bs-success: var(--etat-ok);
  --bs-success-rgb: 74, 124, 89;
  --bs-success-border-subtle: var(--etat-ok);
}

/* ── L'AIDE D'UN CHAMP A UNE TAILLE, PAS DEUX ÉCHELLES MULTIPLIÉES ─────────
   ⛔ `.form-text` EST RELATIVE (`.875em`), ET ELLE COMPOSE. Posée dans un bloc
   déjà réduit — un `.small`, qui vaut lui aussi `.875em` —, elle rend
   9,95 px au lieu des 11,4 px que la charte annonce partout ailleurs (§ « un
   lien posé dans une phrase »). Mesuré sur `?page=config-voip` : la phrase qui
   explique l'essai d'appel est la plus petite du produit, et c'est celle qu'on
   lit quand le clic n'a rien fait.

   ⭐ ELLE HÉRITE, ET C'EST EXACTEMENT LA BONNE VALEUR : `.small` rend déjà
   11,4 px sur le corps de 13 px de la charte — la même taille que
   `.form-text` posée seule. Une aide de champ garde donc UNE taille, où qu'on
   la pose, sans qu'aucun écran ait à savoir dans quoi il l'écrit. */
.small .form-text,
small .form-text {
  font-size: inherit;
}

/* ── ET L'INTERRUPTEUR DEVENAIT BLEU AU CLAVIER ────────────────────────────
   ⛔ LE DERNIER BLEU DE BOOTSTRAP DU PRODUIT, ET IL NE SE VOIT QU'À LA TOUCHE
   TAB. La troisième revue a rendu l'interrupteur droit et son état coché à
   l'encre, et a retiré son halo de focus — mais Bootstrap repeint aussi le
   BOUTON de la piste au focus : `.form-switch .form-check-input:focus` redéclare
   `--bs-form-switch-bg` avec une pastille `#86b7fe` gravée dans l'URL de
   données. Aucun jeton ne l'atteint, et rien ne le montre à la lecture. Mesuré
   au clavier sur les écrans de réglages : neuf interrupteurs virent au bleu dès
   qu'on les atteint — deux sur `?page=config-email-verification`, six sur
   `?page=config-securite`, un sur `?page=config-notifications`.

   ⭐ LE BOUTON PREND L'ENCRE SECONDAIRE, ET LE TRAIT DIT OÙ L'ON EST. Le signal
   de focus est le trait de la charte (§ de la troisième revue) ; le bouton, lui,
   n'a aucune raison de changer de couleur — il garde donc la sienne, en
   `--ink-2` (#4E555B), plus lisible au repos que le gris translucide de
   Bootstrap.
   ⚠️ LA VALEUR EST EN DUR DANS L'URL, ET C'EST LE SEUL ENDROIT DE LA FEUILLE OÙ
   CELA SE FAIT : une `url("data:…")` ne lit pas de `var()`, et `#` s'y écrit
   `%23`. Le jeton reste l'auteur — la valeur est celle de `--ink-2`, recopiée
   ici parce que la syntaxe l'exige.
   ⛔ ET `:not(:checked)` N'EST PAS UNE COQUETTERIE — c'est la moitié de la
   règle. Bootstrap écrit `:focus` ET `:checked` au même poids (0,3,0) et compte
   sur l'ORDRE : le bouton d'un interrupteur ALLUMÉ est blanc, sur une piste
   d'encre. Une règle de la charte au même poids, mais chargée après la sienne,
   gagne alors sur les deux — et le premier jet de ce § peignait le bouton d'un
   interrupteur allumé en #4E555B sur du #1A1C1E : invisible, mesuré ainsi sur
   les quatre interrupteurs cochés de `?page=config-securite`. Le rattrapage ne
   vise donc QUE l'état éteint, et il pèse (0,4,0) pour le dire.
   ⚠️ LE RESTE DU SÉLECTEUR EST CELUI DE BOOTSTRAP, AU PIXEL : `.form-switch
   .form-check-input`. Plus léger, il ne rattrape rien — c'est la leçon que
   `CharteRattrapeBootstrapTest` tient depuis la troisième revue. */
.form-switch .form-check-input:focus:not(:checked) {
  --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%234E555B'/%3e%3c/svg%3e");
}

/* ══ 14.09.2026 — CE QUE LA HUITIÈME REVUE A VU (lot CX) ════════════════════
   Les neuf FICHES et leurs VOLETS rendus dans Chromium (`?page=vehicule`,
   `?page=article`, `?page=collaborateur`, `?page=conducteur`, `?page=location`,
   `?page=livraison`, `?page=atelier-stockage`, `?page=atelier`, `?page=client`)
   à 1 366, 1 920 et 2 560 px, volets FERMÉS puis OUVERTS, au `getComputedStyle`
   et à la touche Tab. Ce qui suit est ce que les JETONS doivent rattraper ; ce
   qui relevait d'un écran a été corrigé dans son écran.

   ⛔ DEUX COULEURS BOOTSTRAP ÉCHAPPAIENT ENCORE AUX JETONS, ET ELLES SE
   VOYAIENT. `--bs-danger` est repris depuis le 22.08.2026 ; ses deux jumeaux ne
   l'étaient pas, et les écrans continuent de les écrire là où la classe porte un
   ÉTAT et non une couleur — `border-warning` sur un document GED reconstitué
   (fiche véhicule, fiche client) et sur le formulaire RGPD, `border-success` sur
   la source de géolocalisation retenue. Mesuré : rgb(255, 193, 7), l'ambre de
   Bootstrap, sur le papier de la charte — quand `--etat-attention` vaut #A8791F
   et `--etat-ok` #4A7C59. Les reprendre ICI vaut pour toute la famille d'une
   teinte (`border-*`, `bg-*-subtle`, `link-*`) sans qu'un écran change de mot.

   ⚠️ CELA N'AUTORISE PAS `text-warning` NI `text-success` DANS LE BALISAGE :
   `HarmonieBootstrapTest` les refuse toujours, et pour une autre raison — un
   rôle n'a qu'une classe. Ce § répare ce qui reste écrit, il n'ouvre rien.

   ⛔ ET `.text-body` RENDAIT UN AUTRE NOIR QUE TOUTE LA PAGE. Bootstrap 5.3
   écrit `.text-body { color: rgba(var(--bs-body-color-rgb), …) }` : c'est le
   jeton en COMPOSANTES qu'il lit, pas `--bs-body-color`, repris plus haut.
   Mesuré sur la bande d'alertes de la fiche véhicule : rgb(33, 37, 41) — le gris
   de la bibliothèque — sur un écran dont tout le reste est en rgb(26, 28, 30).
   Deux noirs à trois centimètres l'un de l'autre. */
:root {
  --bs-body-color-rgb: 26, 28, 30;

  --bs-warning: var(--etat-attention);
  --bs-warning-rgb: 168, 121, 31;
  --bs-success: var(--etat-ok);
  --bs-success-rgb: 74, 124, 89;
}

/* ⛔ LA CROIX D'UN VOLET AVAIT UN COIN, ET ELLE EST SUR TOUS LES VOLETS.
   Bootstrap écrit `.btn-close { border-radius: .375rem }` EN DUR, hors de
   `--bs-border-radius` que la charte met à 0 — même piège que `<kbd>`, corrigé
   au § précédent. Mesuré à 6 px sur les quatre volets du lot (client, véhicule,
   absence, aperçu) et sur chaque dialogue du produit ; le coin se voit au survol,
   quand le fond de la croix apparaît. */
.btn-close { border-radius: var(--coin); }

/* ── LE CHAMP EN LECTURE SEULE N'AVAIT PAS DE TRAIT DE FOCUS ───────────────
   ⛔ ET LE § DU 14.09.2026 LE COMPTAIT PARMI LES SERVIS — c'était faux, mesuré
   faux à la touche Tab sur `?page=atelier` : SIX champs `.form-control-plaintext`
   (nom, prénom, téléphone, plaque, réceptionnaire, total des acomptes) reçoivent
   le focus et ne montrent RIEN. Même cause que pour `.form-control` : Bootstrap
   déclare `.form-control-plaintext:focus { outline: 0 }` — 0,2,0 —, et
   `:focus-visible` (0,1,0) ne reprend jamais la main. Le troisième membre de la
   famille manquait à la correction ; il y entre, au même poids et au même endroit
   de la cascade.
   ⚠️ UN CHAMP QU'ON NE PEUT PAS MODIFIER RESTE UN ARRÊT DE TABULATION, et c'est
   voulu : on le parcourt pour LIRE, et pour copier ce qu'il porte. Un arrêt muet,
   c'est le curseur qui disparaît au milieu d'un formulaire. */
.form-control-plaintext:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
}

/* ══ LA NEUVIÈME REVUE VISUELLE — LA CAISSE, LA COMPTABILITÉ, LES RAPPORTS ══
   ⭐ 15.09.2026 (lot CY). Seize écrans de CHIFFRES rendus dans Chromium avec un
   mois entier de lignes — trente à soixante par tableau, trois taux de TVA, des
   montants à six chiffres, des négatifs, un rapport vide, des raisons sociales
   à « & » —, à 1 366, 1 920 et 2 560 px, au `getComputedStyle`, à la touche Tab
   ET SOUS `emulateMedia({ media: 'print' })`. Ce sont des écrans qu'on LIT et
   qu'on IMPRIME : la moitié de ce qui suit ne se voyait qu'à l'impression, où
   personne n'était encore allé. */

/* ── UN LIEN GARDE LA CHASSE DE SA CELLULE ─────────────────────────────────
   ⛔ UN NUMÉRO DE DOCUMENT CESSAIT D'ÊTRE UN CHIFFRE DÈS QU'IL MENAIT QUELQUE
   PART. Le § du 14.09.2026 (« la fonte de la charte n'atteignait ni les liens
   ni les cellules ») pose `a, th, td { font-family: var(--fonte) }` à
   spécificité (0,0,1) — juste ce qu'il faut pour battre `webbike.css`. Mais
   cette règle s'applique AU LIEN, et elle bat donc l'HÉRITAGE d'un
   `.font-monospace` posé sur la cellule : un `<td class="font-monospace">` qui
   contient `<a>246800</a>` rend la cellule en IBM Plex Mono et son contenu en
   Archivo. La colonne cesse alors d'aligner ses chiffres, et deux cellules
   voisines — l'une liée, l'autre non — n'ont pas la même chasse.
   Mesuré : 46 liens sur `?page=caisse-journal`,
   41 sur `?page=compta-controles`, 32 sur `?page=caisse-transfert`.

   ⭐ LE LIEN N'A PAS D'AVIS SUR LA FONTE : il HÉRITE de ce que la cellule a
   déclaré. Une classe (0,1,1) suffit à le dire, et la règle générale continue
   de tenir partout où rien n'a été déclaré. */
.font-monospace a,
.mono a,
.fig a,
.fig-2 a,
.fig-3 a {
  font-family: inherit;
}

/* ── UN TOTAL SE DIT PAR LE FILET, PAS PAR UNE BANDE GRISE ─────────────────
   ⛔ `.table-light` PEIGNAIT LA TÊTE ET LE PIED DE SIX TABLEAUX EN #F8F9FA —
   le gris 100 de Bootstrap, qui n'est aucun jeton de la maison : mesuré sur
   `?page=caisse-journal` (8 cellules), `?page=compta-vente` et
   `?page=compta-achat` (23), `?page=rapport-mensuel` (12, tête ET pied),
   `?page=statistiques` (6), `?page=controle-factures` (9). La charte donne
   déjà sa forme à la tête — libellé réglé sous un filet d'encre — et
   la règle est qu'un total se distingue « par le filet et le chiffre
   monospacé », jamais par un cran de graisse ni par un fond. Il ne manquait
   que le filet du PIED : le voici, et la bande grise part du balisage. */
.table > tfoot > tr > * {
  border-top: 1px solid var(--ink);
  border-bottom: 0;
}

/* ── UNE LIGNE QUI DEMANDE ATTENTION A LE LAVIS DE L'ATTENTION ─────────────
   ⛔ `.table-warning` RENDAIT L'AMBRE DE BOOTSTRAP (#FFF3CD), pas celui de la
   charte. Le § du 14.09.2026 a remis `--bs-warning` et ses jetons de cadre aux
   encres de la maison, mais Bootstrap écrit les variantes de LIGNE en dur :
   `.table-warning` redéclare `--bs-table-bg` avec une valeur gravée, qui ne lit
   aucun jeton. Mesuré : l'annulation de quittance de `?page=caisse-journal`, le
   transfert à une seule jambe de `?page=caisse-transfert`, le document encore quittancé de
   `?page=nettoyage-factures`. Même geste que `.table-danger`, deux jetons plus
   loin : la ligne garde son sens, elle prend la couleur de la maison. */
.table > tbody .table-warning > td {
  background: var(--etat-attention-w);
}
/* ⚠️ ET LE MÊME OUBLI SUR LES FONDS `-subtle`, que le § du 14.09.2026 avait
   laissés : `bg-warning-subtle` rendait le même ambre de Bootstrap (les bandeaux
   du rapport complet sont passés en `wb-note` le 25.09.2026, lot CPT1). */
:root {
  --bs-warning-bg-subtle: var(--etat-attention-w);
  --bs-success-bg-subtle: var(--etat-ok-w);
}

/* ── UNE TÊTE COLLÉE PREND LE FOND DE CE QU'ELLE SURVOLE ───────────────────
   ⛔ DES TABLEAUX DEMANDAIENT `bg-body` À LEUR `<thead class="sticky-top">`
   (`?page=caisse-transfert`, `?page=compta-controles`).
   `bg-body` lit `--bs-body-bg-rgb`, que la charte ne retaille pas : la bande
   sortait en BLANC PUR quand la page est `--paper` — et ces trois têtes flottent
   au-dessus d'une `.card`, dont le fond est `--panel`. Le fond appartient donc
   à la charte, qui seule sait sur quoi la tête se pose ; le balisage ne demande
   plus de couleur (même leçon que `table-hover`, passée à la charte le
   13.09.2026). ⚠️ TROIS AUTRES ONT SUIVI LE 15.09.2026 (lot DC) :
   `?page=caisse-cloture`, `?page=rappels` et `?page=reappro` gardaient la classe
   parce que l'utilitaire de Bootstrap porte un `!important` et gagnait encore.

   ⭐ ET LE FOND SE POSE SUR LA CELLULE, PAR `--bs-table-bg` (15.09.2026, lot DC
   — vu au défilement). `background` sur le `<thead>` peint le groupe de lignes,
   ce qui SUFFIT à masquer ; mais c'est contre Bootstrap qu'il faut alors gagner,
   et la règle de la maison est de passer par la variable que sa propre règle lit
   (`.table > :not(caption) > * > * { background-color: var(--bs-table-bg) }`) :
   c'est la seule qui ne dépende ni d'un poids ni d'un ordre de feuilles. Même
   geste que la colonne collée du calendrier de location
   (`css/location_calendrier.css`, sixième revue) et que le survol de ligne, qui
   passe par `--bs-table-bg-state`.

   ⛔ ET LE FILET DE LA TÊTE NE SUIVAIT PAS LA TÊTE. Mesuré dans Chromium sur les
   soixante mouvements de `?page=caisse-cloture` : `border-collapse: collapse`
   confie la bordure au TABLEAU, qui la peint aux coordonnées du tableau — la
   tête se colle en haut, son filet d'encre reste en bas avec la ligne qu'il
   séparait, et la bande collée sort sans rien sous son libellé. La charte rend
   donc le filet à la CELLULE, où il voyage avec elle : un dégradé d'un pixel,
   posé sur le fond, à la place de la bordure qu'on retire. ⚠️ `to top` ET NON
   `to bottom` : la première bande d'un dégradé part du bord nommé. */
.table > thead.sticky-top > tr > th {
  --bs-table-bg: var(--panel);
  border-bottom: 0;
  background-image: linear-gradient(to top, var(--rule-2) 1px, transparent 1px);
}

/* ⛔ ET LA TÊTE COLLÉE PASSAIT AU-DESSUS DES MENUS (18.09.2026, vu sur
   `?page=rappels`). L'utilitaire de Bootstrap pose `z-index: 1020` — plus haut
   que ses propres menus déroulants (`--bs-dropdown-zindex: 1000`) : le menu des
   notifications et celui du compte s'ouvraient SOUS la bande « Reste dû ·
   Lettre » sur les sept écrans à tête collée. Une tête de tableau n'a besoin de
   passer que devant les cellules qu'elle survole (z-index automatique) : deux
   suffit, et tout ce qui se déroule, s'ouvre ou commente reste au-dessus. */
.table > thead.sticky-top {
  z-index: 2;
}

/* ⛔ ET SUR LE PAPIER, LA BORDURE REVIENT. Un navigateur n'imprime pas les fonds
   par défaut : le dégradé qui porte le filet à l'écran disparaîtrait de la
   feuille, et la tête d'un rapport imprimé sortirait sans rien sous son libellé.
   Rien ne défile sur une feuille — la raison du dégradé n'y existe pas. */
@media print {
  .table > thead.sticky-top > tr > th {
    border-bottom: 1px solid #000;
    background-image: none;
  }
}

/* ── CE QUI SORT SUR LA FEUILLE ────────────────────────────────────────────
   ⛔ PERSONNE N'ÉTAIT ENCORE ALLÉ VOIR. Le socle d'impression existe depuis le
   Lot 6 (« chrome masqué, papier blanc, encre noire »), et il ne tenait AUCUNE
   de ses trois promesses sur ces seize écrans — parce que rien ne l'avait
   rendu. `body { background: #fff; color: #000 }` pèse (0,0,1) ; le produit
   écrit `<body class="wb-modern">` et la charte lui donne `--paper` en (0,1,1)
   plus haut dans cette feuille — et `css/app.css` la redonne en `!important`
   pour effacer le fond sombre du legacy. Mesuré sous
   `emulateMedia({ media: 'print' })` : fond rgb(250, 249, 246), encre
   rgb(26, 28, 30) — c'est-à-dire du beige et du gris anthracite sur les seize
   écrans, et une cartouche d'encre couleur pour un journal de caisse.

   ⚠️ LE `!important` EST ICI OBLIGÉ, ET C'EST LE TROISIÈME DE LA FEUILLE : la
   règle qu'il corrige en porte un (`body.wb-modern` dans `css/app.css`), et
   l'ordre de chargement ne bat pas un `!important`. Il ne vaut que sous
   `@media print`, où il n'y a plus de thème à suivre : sur une feuille, le fond
   EST le papier. */
@media print {
  body.wb-modern {
    background-color: #fff !important;
    color: #000 !important;
  }

  /* ⛔ UNE LIGNE DE TABLEAU NE SE COUPE PAS EN DEUX PAGES. Sans cela, une
     quittance se lit moitié en bas d'une page et moitié en haut de la suivante —
     mesuré sur les 46 lignes du journal, les 38 de la TVA sur la marge, les 187
     du rapport complet. ⚠️ SUR LA LIGNE, PAS SUR LE TABLEAU NI SUR LA CARTE :
     un bloc de soixante lignes qu'on interdirait de couper ne tiendrait sur
     aucune feuille et déborderait en silence. La tête, elle, se répète déjà
     (`display: table-header-group`, le défaut du navigateur) ; le PIED de total
     se range à la fin plutôt qu'au bas de chaque page. */
  .table > tbody > tr { break-inside: avoid; }
  .table > tfoot { display: table-footer-group; }

  /* ⛔ ET LE SOULIGNEMENT DE BOOTSTRAP REVENAIT DANS LES GRILLES. Sa feuille
     d'impression pose `a:not(.btn)` en souligné, à (0,1,1), qui passe au-dessus
     de tout : le journal de caisse sort avec 92 liens soulignés — le numéro de
     document ET le nom du client de chaque ligne —, c'est-à-dire l'écran rayé
     que le § du 14.09.2026 refuse à l'écran. Dans une grille, un lien se
     reconnaît à sa COLONNE, sur le papier comme à l'écran ; et sur une feuille
     il ne mène nulle part. */
  .table a:not(.btn) { text-decoration: none; }

  /* ⛔ LES CRITÈRES S'IMPRIMENT, MAIS PAS EN FORMULAIRE. Un rapport qui ne dit
     pas SA PÉRIODE ne vaut rien une fois posé sur un bureau : la barre d'outils
     reste. Mais elle sortait en CONTRÔLES — des cadres de champ date avec leur
     petit calendrier gravé, jusqu'à huit sur le journal de caisse. On garde la
     valeur, on retire le widget ; le filet de la barre part avec lui, la page
     ayant déjà celui de son titre. */
  .wb-outils { border-bottom: 0; }
  .wb-outils .form-control,
  .wb-outils .form-select {
    border: 0;
    padding-left: 0;
    padding-right: 0;
    background: transparent;
    appearance: none;
  }
  .wb-outils select,
  .wb-outils input[type="date"]::-webkit-calendar-picker-indicator {
    background-image: none;
  }
}

/* ══ 15.09.2026 — CE QUE LA DIXIÈME REVUE A VU (lot CZ) ═════════════════════
   Les vingt écrans du MAGASIN et des OUTILS — les deux bandes de scan, le
   réapprovisionnement, les quatre écrans d'import, le décodeur VIN, les
   messages, les rappels, les cibles de campagne, la qualité du fichier, les
   téléphones à corriger, le lecteur d'aide —, rendus à 1 366, 1 920 et 2 560 px
   avec des données plausibles, au `getComputedStyle` et à la touche Tab. C'est
   la famille que personne n'avait jamais rendue, et elle portait les DERNIÈRES
   couleurs de la planche Bootstrap du produit. */

/* ── LE FOND SUBTIL D'UN ÉTAT N'ÉTAIT PAS REPRIS, ET DEUX ÉCRANS JUMEAUX
   S'ALLUMAIENT DE DEUX VERTS ───────────────────────────────────────────────
   ⛔ MESURÉ SUR LES DEUX BANDES DE SCAN, VOISINES DANS LA MÊME SOUS-NAVIGATION.
   `?page=inventaire` peint sa carte avec les jetons de la maison
   (`--etat-ok-w` → rgb(232, 240, 234)) ; `?page=reception`, qui fait le MÊME
   geste — on scanne, la carte s'allume —, lisait `--bs-success-bg-subtle`, que
   la charte ne reprenait pas : rgb(209, 231, 221), le vert de la bibliothèque.
   Idem pour l'ambre (255, 243, 205 contre 247, 240, 223) et pour le rouge
   (248, 215, 218 contre 251, 236, 234).

   ⚠️ LE § DU 14.09.2026 AVAIT REPRIS LE NOM ET LES COMPOSANTES de `--bs-warning`
   et `--bs-success` — donc les FILETS des deux cartes s'accordaient déjà, et
   seuls leurs FONDS divergeaient. Une famille reprise à moitié est plus
   trompeuse qu'une famille intacte : on croit la couleur tenue parce qu'on en
   voit une moitié juste. */
:root {
  --bs-success-bg-subtle:   var(--etat-ok-w);
  --bs-warning-bg-subtle:   var(--etat-attention-w);
  --bs-danger-bg-subtle:    var(--red-w);
  --bs-secondary-bg-subtle: var(--wash);
  --bs-primary-bg-subtle:   var(--wash);
  --bs-info-bg-subtle:      var(--wash);
}

/* ── LA TÊTE D'UN TABLEAU RENDAIT LE GRIS DE BOOTSTRAP ET DU NOIR PUR ───────
   ⛔ `.table-light` POSE `--bs-table-bg: #f8f9fa` ET `--bs-table-color: #000`,
   en dur, variante par variante — même piège que les jetons de bouton du § de
   la troisième revue. La charte remet `--bs-table-bg` sur `.table`, mais une
   variante posée sur le `<thead>` la bat : mesuré rgb(248, 249, 250) sur la
   table de l'import de véhicules et sur celle du décodeur VIN, avec un texte en
   noir pur au milieu d'un écran en rgb(26, 28, 30). Vingt écrans du produit
   coiffent ainsi leur tableau.
   ⚠️ LES QUATRE VARIANTES EMPLOYÉES SONT REPRISES ENSEMBLE : `-light` (une
   tête), `-secondary` (une ligne de séparation), `-warning` (une ligne qui
   diverge) et `-active` (la ligne que le clavier vise). Aucune n'apporte de
   teinte neuve — elles lisent les jetons que la charte a déjà. */
.table-light,
.table-secondary {
  --bs-table-bg: var(--wash);
  --bs-table-color: var(--ink);
  --bs-table-border-color: var(--rule);
  --bs-table-striped-bg: var(--wash);
  --bs-table-hover-bg: var(--wash);
  color: var(--ink);
  border-color: var(--rule);
}
.table-warning {
  --bs-table-bg: var(--etat-attention-w);
  --bs-table-color: var(--ink);
  --bs-table-border-color: var(--rule);
  --bs-table-striped-bg: var(--etat-attention-w);
  --bs-table-hover-bg: var(--etat-attention-w);
  color: var(--ink);
  border-color: var(--rule);
}
.table {
  --bs-table-active-bg: var(--wash);
  --bs-table-active-color: var(--ink);
}

/* ── UN NOMBRE ALIGNÉ À DROITE N'ÉTAIT PAS TABULAIRE ────────────────────────
   ⛔ ALIGNER À DROITE NE SERT À RIEN SI LES CHIFFRES N'ONT PAS LA MÊME CHASSE.
   Les grilles Tabulator sont tabulaires depuis la quatrième revue ; les `.table`
   de Bootstrap, non — mesuré sur le rapport d'import (90 cellules de prix et de
   comptes), sur le journal des retours de mailing et sur les deux bandes de
   scan. Une colonne de prix en Archivo proportionnel ne s'aligne que sur son
   dernier chiffre : « 1 929 » et « 41 207 » ne se comparent plus d'un coup
   d'œil, ce qui est la seule raison de les mettre en colonne.
   ⚠️ LA RÈGLE SUIT `text-end`, PAS LE CONTENU : c'est la DÉCLARATION de l'écran
   — « cette colonne est un nombre » — qui la déclenche, comme `filterType:
   'range'` au registre des grilles. Un texte pris dedans ne bouge pas d'un
   pixel. */
.table > thead th.text-end,
.table > tbody td.text-end,
.table > tfoot td.text-end { font-variant-numeric: tabular-nums; }

/* ── UNE CASE À COCHER RÉTRÉCISSAIT AVEC LE TEXTE QUI LA PORTE ──────────────
   ⛔ `.form-check-input` FAIT `1em` : sa taille est celle de la fonte HÉRITÉE,
   donc celle du bloc où on l'écrit. Mesuré à 9,5 px dans l'en-tête de tableau du
   réapprovisionnement — c'est le « tout cocher » de la proposition, la case la
   plus cliquée de l'écran, rendue à la taille d'un libellé réglé — et à 11,4 px
   dans les rangées `.small` des téléphones à corriger. La cible tombe alors sous
   les dix pixels, au doigt comme à la souris.
   ⚠️ LA TAILLE EST CELLE DU CORPS, pas une valeur neuve : la case rend partout
   ce qu'elle rend dans un formulaire ordinaire. */
.form-check-input { font-size: var(--bs-body-font-size); }

/* ── LE FOCUS D'UN ONGLET ET CELUI D'UNE CROIX ÉTAIENT DES HALOS BLEUS ──────
   ⛔ DEUX FAMILLES DE CONTRÔLE MANQUAIENT À L'APPEL du § du 14.09.2026, qui
   nomme `.form-control`, `.form-select`, `.btn`, `.btn-check` et
   `.form-check-input`. Bootstrap éteint l'`outline` et pose une ombre de quatre
   pixels en rgba(13, 110, 253, .25) sur `.nav-link` ET sur `.btn-close` :
   mesuré à la touche Tab sur les onglets du rapport d'import et sur la croix des
   volets — c'est-à-dire sur le premier contrôle d'une page à onglets et sur le
   seul moyen de refermer un volet au clavier. Ce bleu-là n'apparaît QUE sous le
   clavier : aucune capture ne le montre, seule la touche Tab le trouve. */
.nav-link:focus-visible,
.accordion-button:focus-visible,
.btn-close:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 1px;
  box-shadow: none;
}

/* ── LE FILET DU HAUT D'UNE GRILLE ÉTAIT RESTÉ CELUI DU THÈME ──────────────
   ⛔ LE § DE LA QUATRIÈME REVUE A REPRIS LE FILET DU BAS de `.tabulator-header`
   — celui qui sépare les titres des lignes — et laissé celui du HAUT : mesuré
   rgb(222, 226, 230), le gris 300 de Bootstrap, sur les quatorze grilles du
   produit, à un pixel d'un cadre en rgb(226, 223, 215). Deux gris voisins pour
   un même trait, c'est le défaut que la charte existe pour supprimer. */
.tabulator .tabulator-header { border-top-color: var(--rule); }

/* ── LA BARRE DE PROGRESSION ÉTAIT VERTE, ET SON FOND GRIS ──────────────────
   ⛔ `.progress` PORTE `--bs-progress-bg: var(--bs-secondary-bg)` ET UN COIN,
   et l'import d'articles peignait sa barre en `bg-success`. Une progression
   n'est pas un succès : elle avance. Elle avance donc à l'encre, sur le lavis de
   la charte et sans coin — c'est la jauge-trait du § « Le tableau », en plus
   épais. */
.progress,
.progress-stacked {
  --bs-progress-bg: var(--wash);
  --bs-progress-bar-bg: var(--ink);
  --bs-progress-border-radius: var(--coin-s);
  --bs-progress-box-shadow: none;
  --bs-progress-bar-color: var(--paper);
}

/* ── L'ACCORDÉON ÉTAIT LE DERNIER COMPOSANT NON HABILLÉ ─────────────────────
   ⛔ UN SEUL ÉCRAN EN POSE UN — la documentation de format de l'import
   d'articles —, et il arrivait avec tout le décor de la bibliothèque : coins,
   ombre au repos, fond bleu pâle et texte bleu à l'ouverture, halo bleu au
   focus, et un bouton à 16 px quand le corps de la charte est à 13. Il prend les
   jetons de la maison, comme le calendrier et la grille avant lui. */
.accordion {
  --bs-accordion-border-color: var(--rule);
  --bs-accordion-border-radius: var(--bs-border-radius-lg);
  --bs-accordion-inner-border-radius: calc(var(--bs-border-radius-lg) - 1px);
  --bs-accordion-bg: var(--panel);
  --bs-accordion-color: var(--ink);
  --bs-accordion-btn-color: var(--ink);
  --bs-accordion-btn-bg: var(--wash);
  --bs-accordion-btn-focus-box-shadow: none;
  --bs-accordion-active-bg: var(--wash);
  --bs-accordion-active-color: var(--ink);
}
/* ⚠️ ET SON EN-TÊTE EST UN `<h2>` SANS CLASSE : il prenait l'échelle de titre de
   la page (32 px) pour une ligne dont le texte vit dans le bouton. */
.accordion-header { font-size: inherit; margin-bottom: 0; }
.accordion-button { font-size: var(--bs-body-font-size); box-shadow: none; }

/* ── UN TABLEAU ÉCRIT À LA MAIN DONT LA LIGNE OUVRE (16.09.2026, lot EL) ─────
   ⭐ LA DÉCISION DU LOT EH VAUT POUR TOUTE LISTE : on ouvre au clic sur la
   LIGNE, pas sur la seule cellule qui porte le lien. `js/grille-clavier.js`
   la tient pour les grilles Tabulator (`.wb-dt-ligne-ouvrable`, `css/app.css`),
   qui ont leurs propres classes ; les tableaux `<table>` écrits à la main n'en
   avaient aucune — deux tableaux d'ARGENT en étaient restés à la cellule.
   ⚠️ LA MARQUE EST SUR LE CONTENEUR, posée par `wbFile.tableau()` : le journal
   de caisse refabrique son corps à chaque recherche, et une classe posée ligne
   par ligne ne survivrait pas au premier rendu.
   ⚠️ NI FOND NI PASTILLE : la main avant le clic, le trait de la charte au
   clavier — exactement ce que la grille dit déjà. */
.wb-dt-lignes-ouvrables tbody tr { cursor: pointer; }
.wb-dt-lignes-ouvrables tbody tr:focus-visible {
  outline: 2px solid var(--actif, currentColor);
  outline-offset: -2px;
}

/* ── LE CARRÉ DE COULEUR D'UNE FICHE (17.09.2026, lot EW) ───────────────────
   ⭐ « GRIS » ÉCRIT NE MONTRE PAS LE GRIS. La grille rend la teinte d'un
   véhicule en petit carré depuis le 11.08.2026 ; la fiche — l'écran qu'on a
   devant soi quand on a la moto devant soi — n'en rendait aucun.
   ⛔ CE N'EST PAS UN ÉTAT, C'EST UNE VRAIE COULEUR : le carré porte la teinte
   elle-même, et rien d'autre. Il ne prend donc ni coin, ni ombre, ni fond de
   composant — sa seule décoration est le filet d'encre de la charte, qui existe
   pour qu'un blanc ou un crème ne disparaisse pas sur le papier.
   ⚠️ IL EST PLUS GRAND QUE CELUI DE LA GRILLE, et c'est la place qui le dit :
   dans une cellule de 12 px de texte, le carré accompagne ; à côté d'un champ de
   formulaire, il RÉPOND — « la teinte que vous venez de choisir, la voilà ».
   ⚠️ ET LA PALETTE N'EST PAS ICI : elle vient de `WB_COULEURS_PASTILLES`
   (`lib/couleur_normalisation.inc.php`), transportée par le carré. */
.wb-couleur-carre {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-top: 4px;
  min-height: 1rem;
}
.wb-couleur-carre .wb-pastille {
  width: 1.25rem;
  height: 1.25rem;
  margin: 0;
  border-radius: var(--coin-s);
  box-shadow: none;
  border: 1px solid var(--rule);
  vertical-align: middle;
}
/* Une teinte DÉDUITE du code ne se lit pas comme une teinte SAISIE : filet
   esquissé, teinte inchangée — c'est son statut qui change, pas sa couleur. */
.wb-couleur-carre .wb-pastille-deduite { border-style: dashed; }

/* ══ LE BLOC « PRISE EN CHARGE PAR UN TIERS » (18.09.2026, lot PC1) ══════════
   ⭐ IL NE PREND PAS DE PLACE, ET C'EST SA PREMIÈRE PROPRIÉTÉ : quatre-vingt-
   quinze pour cent des documents n'ont pas de dossier, et le bloc n'est alors
   qu'un bouton-lien. La ligne de résumé porte le filet de gauche du Registre —
   ni fond, ni coin, ni ombre —, et le formulaire en place se sépare de ce qui
   le précède par un filet, pas par une boîte. Composant : `js/prise-en-charge.js`. */
.wb-pec-ligne {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem;
  border-left: 2px solid var(--rule);
  padding-left: .5rem;
}
.wb-pec-ligne:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}
.wb-pec-form {
  border-top: 1px solid var(--rule);
  padding-top: .5rem;
}

/* ══ L'AIDE EN LIGNE (18.09.2026, lot AI) ════════════════════════════════════
   ⛔ CES RÈGLES VIVAIENT DANS LE `<style>` DE `app/pages/aide.php`, et l'une
   d'elles y avait déjà battu la charte à poids égal (une largeur de lecture
   écrite sur place, rendue APRÈS la feuille, qui donnait 832 px là où
   `.wb-lecture` en veut 704). Une déclaration d'écran qui décide de la même
   chose qu'un jeton de la charte gagne toujours, et personne ne va la chercher
   dans le corps d'une page. Elles sont ici, avec le reste du système. */

/* Le sujet ouvert se marque comme la ligne choisie d'un registre : un filet
   d'encre dans la marge, jamais un fond. Le filet est posé sur TOUTES les
   lignes, transparent par défaut, pour que l'ouverture d'un sujet ne décale
   pas les autres de trois pixels. */
.wb-aide-autres > li > a { border-left: 3px solid transparent; padding-left: .5rem; }
.wb-aide-autres > li > a.wb-aide-ouvert { border-left-color: var(--acc); font-weight: 600; }

/* Rendu du texte d'aide : lisible d'abord, décoré ensuite. La largeur de
   lecture, elle, reste celle de `.wb-lecture` — elle ne se redéclare pas ici. */
.wb-aide-corps h2.wb-aide-titre { font-size: 1.05rem; font-weight: 600; margin: 1.75rem 0 .6rem; }
.wb-aide-corps h3.wb-aide-titre { font-size: .95rem; font-weight: 600; margin: 1.25rem 0 .5rem; }
.wb-aide-corps h2.wb-aide-titre:first-child,
.wb-aide-corps h3.wb-aide-titre:first-child { margin-top: 0; }
.wb-aide-corps p, .wb-aide-corps li { font-size: .9rem; line-height: 1.6; }
.wb-aide-corps ul, .wb-aide-corps ol { padding-left: 1.25rem; }
.wb-aide-corps code { font-size: .85em; }
.wb-aide-corps .wb-aide-table { font-size: .85rem; }

/* ── TROIS INTENTIONS, TROIS FILETS ────────────────────────────────────────
   ⛔ UN SEUL ASPECT POUR TROIS SENS EST UNE LISTE FAUSSE À L'ŒIL. Les sujets
   ouvrent leurs encadrés par un pictogramme — « c'est refusé », « attention à
   ceci », « bon à savoir » — et les trois sortaient du même filet ambre : rien
   ne distinguait un refus d'un conseil sans les lire tous les trois.
   ⚠️ DEUX TONS DE LA CHARTE, PAS TROIS COULEURS INVENTÉES : le rouge du refus,
   l'ambre de l'attention, l'encre pour le reste — exactement les filets que
   `.wb-note` connaît déjà. C'est le MOT (`.lbl`) qui porte la troisième
   distinction, pas une teinte de plus.
   ⚠️ ET PAS DE FOND : une note de la charte est du texte courant tenu par un
   filet ; le lavis en faisait une boîte, c'est-à-dire un composant de plus pour
   la même nature d'information (règle 6). */
.wb-aide-corps .wb-aide-encadre {
  border-left: 3px solid var(--rule);
  padding: 0 0 0 .75rem;
  margin: 1rem 0;
  font-size: .88rem;
  color: var(--ink-2);
}
.wb-aide-corps .wb-aide-encadre > .lbl { display: block; }
.wb-aide-corps .wb-aide-encadre--refus { border-left-color: var(--red); }
.wb-aide-corps .wb-aide-encadre--refus > .lbl { color: var(--red); }
.wb-aide-corps .wb-aide-encadre--attention { border-left-color: var(--etat-attention); }
.wb-aide-corps .wb-aide-encadre--conseil { border-left-color: var(--acc); }

/* ── LE FILTRE DE L'INDEX ──────────────────────────────────────────────────
   ⚠️ UNE CARTE DE GROUPE VIDE SE CACHE, ET IL FAUT LE DIRE EN CSS : Bootstrap
   pose `display: flex` sur `.card`, et une classe bat l'`[hidden]` de la
   feuille du navigateur. Sans cette ligne, filtrer laisserait dix cadres vides
   empilés sous la liste — la même leçon que `.wb-detail[hidden]`. */
[data-wb-aide-groupe][hidden] { display: none !important; }
/* Le champ ne prend pas la largeur de l'écran : on y tape trois lettres. */
.wb-aide-filtre > .form-control { max-width: 28rem; }

/* ── « DANS CE SUJET » — le sommaire suit le défilement ────────────────────
   ⚠️ MÊME GESTE QUE `.wb-detail` : collé sous la manchette, borné à la hauteur
   qui reste, et défilant pour lui-même quand il déborde. Un sommaire qui
   disparaît au premier écran ne sert qu'au premier paragraphe. */
.wb-aide-marge {
  position: sticky;
  top: 0.5rem;
  max-height: calc(100vh - var(--h-pied) - 1rem);
  overflow-y: auto;
}
@media (max-width: 991.98px) {
  /* Sous le point de rupture, la colonne passe SOUS le texte : la coller
     n'aurait plus rien à suivre, et la borne de hauteur couperait la liste. */
  .wb-aide-marge { position: static; max-height: none; overflow-y: visible; }
}
/* Le titre en cours de lecture porte le filet d'encre de la ligne choisie —
   et rien d'autre : ni fond, ni seconde couleur. */
.wb-aide-sommaire > ul > li > a { border-left: 3px solid transparent; padding-left: .5rem; }
.wb-aide-sommaire .wb-aide-t3 > a { padding-left: 1.25rem; }
.wb-aide-sommaire > ul > li > a.wb-aide-encours {
  border-left-color: var(--acc);
  font-weight: 600;
}

/* ⭐ LA QUITTANCE ANNULÉE DU JOURNAL DE CAISSE (U46, 27.09.2026) : listée,
   barrée, hors des totaux. Sa trace (« Annulée le … par … ») est un
   `inline-block`, que le barré de la ligne ne traverse pas. */
.wb-quittance-annulee td { text-decoration: line-through; color: var(--ink-2); }
.wb-quittance-annulee .wb-annulee-trace { display: inline-block; text-decoration: none; }

/* ── Connexion : une carte posée sur le fond, le bouton d'entrée à l'accent. */
body.login-page {
  background: var(--paper);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
.login-card {
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--coin-l);
  width: 100%;
  max-width: 380px;
  padding: 2rem 2rem 1.5rem;
}
.login-logo {
  display: block;
  margin: 0 auto 1.75rem;
  max-height: 34px;
  max-width: 180px;
  object-fit: contain;
}
.login-title { text-align: center; margin-bottom: 1.5rem; }
.login-surtitre {
  display: block;
  font-size: 12.5px;
  color: var(--ink-3);
  margin-bottom: .15rem;
}
.login-title strong {
  display: block;
  font-size: 22px;
  line-height: 1.2;
  color: var(--ink);
  font-weight: 700;
}
/* Le bouton d'entrée et la tuile d'un compte enrôlé se visent au doigt. */
.btn-login,
.pin-tile { --bs-btn-font-size: 1.125rem; }
.btn-login {
  --bs-btn-bg: var(--acc);
  --bs-btn-border-color: var(--acc);
  --bs-btn-color: #fff;
  --bs-btn-hover-bg: var(--acc-h);
  --bs-btn-hover-border-color: var(--acc-h);
  --bs-btn-hover-color: #fff;
  --bs-btn-active-bg: var(--acc-h);
  --bs-btn-active-border-color: var(--acc-h);
  --bs-btn-active-color: #fff;
  font-weight: 600;
  padding: .55rem;
}
.login-lien {
  display: block;
  text-align: center;
  margin-top: .9rem;
  font-size: 12.5px;
  color: var(--ink-2);
}
.login-version {
  text-align: center;
  font-size: 12px;
  color: var(--ink-3);
  border-top: 1px solid var(--rule);
  padding-top: .75rem;
  margin-top: 1.5rem;
}

/* ══ LE PLANNING A2 — jours en lignes, une rangée par mécanicien ════════════
   Maquette `docs/charte/planning-a2.html`. Le type donne le fond (`--ty-*`,
   nommé par la teinte de `atelier_types_rdv`), l'avancement remplit la pastille. */

:root {
  --ty-gris-f: #FFFFFF;   --ty-gris-b: #CDD3DB;   --ty-gris-a: #6B7480;
  --ty-orange-f: #FFE7CF; --ty-orange-b: #F2B57A; --ty-orange-a: #C2620A;
  --ty-rouge-f: #FCDDE0;  --ty-rouge-b: #EE9BA4;  --ty-rouge-a: #B4232F;
  --ty-violet-f: #ECE3FA; --ty-violet-b: #BFA8EA; --ty-violet-a: #6B46B5;
  --ty-bleu-f: #DDEBFB;   --ty-bleu-b: #9DC0EE;   --ty-bleu-a: #1F5FBF;
  --ty-vert-f: #DDF2E3;   --ty-vert-b: #9ED3AE;   --ty-vert-a: #1E7B4B;

  --av-nonrecu: #8A94A1; --av-recu: #C98A00; --av-cours: #1F5FBF;
  --av-fini: #1E7B4B; --av-paye: #9AA3AE;
  /* La barre de l'avancée du jour distingue les trois verts. */
  --av-barre-nonrecu: #D5DAE0; --av-barre-fini: #6DBB8A; --av-barre-facture: #145A36;

  --pl-chip-h: 36px; --pl-chip-w: 200px; --pl-pad: 3px; --pl-fs: 14px; --pl-fs-2: 12.5px;
}
.wb-pl.wb-pl-confort { --pl-chip-h: 58px; --pl-chip-w: 236px; --pl-pad: 5px; --pl-fs: 15px; --pl-fs-2: 13.5px; }

.wb-pl { display: flex; flex-direction: column; min-height: 0; font-variant-numeric: tabular-nums; }
.wb-pl-outils { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 12px;
  background: var(--panel); border: 1px solid var(--rule); border-bottom: 0; }
.wb-pl-outils .wb-pl-droite { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.wb-pl-outils kbd { font: 12px var(--fonte); border: 1px solid var(--rule-2); border-radius: var(--coin-s);
  padding: 0 4px; color: var(--ink-3); background: var(--paper); }
.wb-pl-outils .btn-primary kbd { background: transparent; color: var(--panel); border-color: var(--panel); }
.wb-pl-recherche { width: 20rem; max-width: 100%; }
.wb-pl-filtres { display: flex; flex-wrap: wrap; gap: 4px; }
.wb-pl-ftype { height: 28px; padding: 0 8px 0 6px; border-radius: 12px; border: 1px solid var(--b);
  background: var(--f); font: inherit; font-size: 13.5px; display: inline-flex; align-items: center;
  gap: 5px; cursor: pointer; color: var(--ink); }
.wb-pl-ftype i { width: 10px; height: 10px; border-radius: var(--coin-s); background: var(--a); }
.wb-pl-ftype[aria-pressed="false"] { opacity: .35; }

/* L'avancée du jour : tous les ordres d'aujourd'hui, par étape. */
.wb-pl-avancee { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; padding: 7px 12px;
  background: var(--panel); border: 1px solid var(--rule); border-bottom: 0; font-size: 13px; color: var(--ink-2); }
.wb-pl-avancee b { color: var(--ink); }
.wb-pl-av-barre { flex: 1 1 300px; display: flex; height: 14px; border-radius: 7px; overflow: hidden; background: var(--rule); }
.wb-pl-av-barre span { display: block; height: 100%; }
.wb-pl-av-cpt { display: flex; flex-wrap: wrap; gap: 4px 12px; }
.wb-pl-av-cpt span { display: inline-flex; align-items: center; gap: 5px; }
.wb-pl-av-cpt i { width: 10px; height: 10px; border-radius: var(--coin-s); }
.wb-pl-av-cpt .zero { opacity: .4; }
.wb-pl-t-nonrecu { background: var(--av-barre-nonrecu); }
.wb-pl-t-recu { background: var(--av-recu); }
.wb-pl-t-cours { background: var(--av-cours); }
.wb-pl-t-fini { background: var(--av-barre-fini); }
.wb-pl-t-recupere { background: var(--av-fini); }
.wb-pl-t-facture { background: var(--av-barre-facture); }
.wb-pl-t-paye { background: var(--av-paye); }

/* La grille et le volet (`.wb-maitre-detail`) : la charte A2 donne 360 px au volet. */
.wb-pl-corps { border: 1px solid var(--rule); background: var(--panel); }
.wb-pl-defile { --pl-cols: 104px 108px minmax(0, 1fr) 86px 170px;
  overflow-y: auto; max-height: calc(100vh - 220px); position: relative; }
.wb-pl-entete, .wb-pl-jour { display: grid; grid-template-columns: var(--pl-cols); }
.wb-pl-entete { position: sticky; top: 0; z-index: 5; background: var(--paper);
  border-bottom: 1px solid var(--rule-2); font-size: 13px; font-weight: 600; color: var(--ink-2); }
.wb-pl-entete > div { padding: 5px 8px; }
.wb-pl-jour { border-bottom: 1px solid var(--rule-2); background: var(--panel); }
.wb-pl-jour.lundi { border-top: 2px solid var(--ink); }
.wb-pl-jour.passe { background: var(--paper); }
.wb-pl-jour.auj { background: var(--repere-aujourdhui-w); }
.wb-pl-jour.ferme { background: var(--trame-indispo); }
.wb-pl-dl { grid-column: 1; padding: 6px 8px; border-right: 1px solid var(--rule);
  position: sticky; top: 27px; align-self: start; z-index: 2; }
.wb-pl-dl .j { font-weight: 700; font-size: 14.5px; }
.wb-pl-dl small { display: block; font-weight: 500; color: var(--ink-3); font-size: 12.5px; }
.wb-pl-dl .cap { margin-top: 5px; font-size: 12.5px; color: var(--ink-2); }
.wb-pl-barre { height: 5px; background: var(--rule); border-radius: 3px; overflow: hidden; margin-top: 3px; }
.wb-pl-barre span { display: block; height: 100%; background: var(--ink-3); }
.wb-pl-barre.plein span { background: var(--etat-attention); }
.wb-pl-barre.trop span { background: var(--red); }
.wb-pl-lignes { grid-column: 2 / span 3; display: grid; min-width: 0; }
.wb-pl-ligne { display: grid; grid-template-columns: 108px minmax(0, 1fr) 86px;
  border-bottom: 1px solid var(--rule); min-height: calc(var(--pl-chip-h) + 2 * var(--pl-pad)); }
.wb-pl-ligne:last-child { border-bottom: 0; }
.wb-pl-ligne.absent { background: var(--trame-indispo); }
.wb-pl-nom { padding: var(--pl-pad) 8px; font-weight: 600; font-size: 13.5px; display: flex;
  flex-direction: column; justify-content: center; border-right: 1px solid var(--rule); min-width: 0; }
.wb-pl-nom em { font-style: normal; font-weight: 400; color: var(--ink-3); font-size: 12.5px; }
.wb-pl-nom.placer { color: var(--red); }
.wb-pl-rdvs { display: flex; flex-wrap: wrap; gap: 3px; padding: var(--pl-pad) 6px; align-content: flex-start; min-width: 0; }
.wb-pl-rdvs.cible { outline: 2px dashed var(--acc); outline-offset: -3px; background: var(--acc-w); }
.wb-pl-lc { padding: var(--pl-pad) 8px; font-size: 12.5px; color: var(--ink-2);
  border-left: 1px solid var(--rule); display: flex; flex-direction: column; justify-content: center; }
.wb-pl-lc.trop { color: var(--red); font-weight: 600; }
.wb-pl-note { grid-column: 5; border-left: 1px solid var(--rule); padding: 5px 8px; font-size: 13px;
  color: var(--ink-2); white-space: pre-wrap; overflow-wrap: anywhere; min-width: 0; }
.wb-pl-note[contenteditable="true"]:focus { outline: 2px solid var(--acc); outline-offset: -2px; background: var(--panel); }
.wb-pl-note:empty::before { content: attr(data-vide); color: var(--ink-3); opacity: .6; }

/* Le rendez-vous : largeur fixe, deux lignes en Dense, trois en Confort. */
.wb-pl-rdv { --f: var(--ty-gris-f); --b: var(--ty-gris-b); --a: var(--ty-gris-a);
  height: var(--pl-chip-h); width: var(--pl-chip-w); display: inline-flex; flex-direction: column;
  justify-content: center; gap: 1px; padding: 0 24px 0 5px; border-radius: 4px; background: var(--f);
  border: 1px solid var(--b); color: var(--ink); cursor: pointer; white-space: nowrap; position: relative;
  font-size: var(--pl-fs); line-height: 1.2; text-align: left; font-family: inherit; }
.wb-pl-rdv[draggable="true"] { cursor: grab; }
.wb-pl-rdv:focus-visible, .wb-pl-rdv.choisi { outline: 2px solid var(--acc); outline-offset: 1px; }
.wb-pl-rdv .l1, .wb-pl-rdv .l2, .wb-pl-rdv .l3 { display: flex; gap: 5px; align-items: center; min-width: 0; overflow: hidden; }
.wb-pl-rdv .l2, .wb-pl-rdv .l3 { font-size: var(--pl-fs-2); color: var(--ink-2); }
.wb-pl-rdv .l3 { display: none; }
.wb-pl-confort .wb-pl-rdv .l3 { display: flex; }
.wb-pl-rdv .cl, .wb-pl-rdv .mo, .wb-pl-rdv .det { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.wb-pl-rdv .n { font-weight: 600; color: var(--ink-2); flex: none; }
.wb-pl-rdv .cl { font-weight: 600; }
.wb-pl-rdv .h, .wb-pl-rdv .ic { color: var(--ink-2); flex: none; display: inline-flex; gap: 3px; align-items: center; }
.wb-pl-hf { font-weight: 700; font-size: 12px; line-height: 15px; padding: 0 3px; border-radius: var(--coin-s);
  border: 1px solid var(--ink); background: var(--panel); color: var(--ink); flex: none; }
.wb-pl-tag { font-weight: 600; font-size: 12px; line-height: 15px; padding: 0 3px; border-radius: var(--coin-s);
  background: var(--a); color: var(--panel); flex: none; }
.wb-pl-att { display: inline-grid; place-items: center; width: 17px; height: 15px; border-radius: var(--coin-s);
  background: var(--red); color: var(--panel); flex: none; font-size: 11px; }
.wb-pl-arb { color: var(--etat-attention); flex: none; }
.wb-pl-rdv.urg .n { color: var(--panel); background: var(--red); border-radius: var(--coin-s); padding: 0 3px; }
.wb-pl-rdv.paye { opacity: .5; }
/* Non réceptionné après sa date : le client n'est pas venu, l'ordre attend une décision. */
.wb-pl-rdv.oublie { border: 1.5px solid var(--red); background: linear-gradient(90deg, var(--red-w), var(--f)); }
.wb-pl-rdv .ic .pret-ko { color: var(--red); }

/* La pastille d'avancement se remplit d'un cinquième par étape. */
.wb-pl-av { display: inline-block; width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--c);
  background: conic-gradient(var(--c) var(--p), var(--panel) 0); flex: none; vertical-align: -2px; }
.wb-pl-rdv .wb-pl-av { position: absolute; right: 5px; top: 50%; margin-top: -7px; }
.wb-pl-av.nonrecu { --c: var(--av-nonrecu); --p: 0%; }
.wb-pl-av.recu { --c: var(--av-recu); --p: 20%; }
.wb-pl-av.cours { --c: var(--av-cours); --p: 40%; }
.wb-pl-av.fini { --c: var(--av-fini); --p: 60%; }
.wb-pl-av.recupere { --c: var(--av-fini); --p: 80%; }
.wb-pl-av.facture { --c: var(--av-fini); --p: 100%; }
.wb-pl-av.paye { --c: var(--av-paye); --p: 100%; }
.wb-pl-av.oublie { --c: var(--red); }

/* La vue « Jour » : la colonne des rendez-vous devient une frise de 7 h 30 à 18 h. */
.wb-pl-heures { position: relative; }
.wb-pl-vue-jour .wb-pl-heures span { position: absolute; top: 5px; transform: translateX(-50%); font-weight: 500; font-size: 12px; }
.wb-pl-frise { position: relative; display: block; padding: 0; min-height: calc(var(--pl-chip-h) + 2 * var(--pl-pad));
  background: repeating-linear-gradient(90deg, transparent 0 calc(100% / 21 - 1px), var(--rule) calc(100% / 21 - 1px) calc(100% / 21)); }
.wb-pl-frise .wb-pl-rdv { position: absolute; top: var(--pl-pad); min-width: 0; padding-right: 20px; z-index: 1; }
.wb-pl-frise .pause { position: absolute; top: 0; bottom: 0; background: var(--trame-indispo); }
.wb-pl-frise .maint { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--red); z-index: 3; pointer-events: none; }
.wb-pl-ligne:first-child .wb-pl-frise .maint::before { content: attr(data-h); position: absolute; top: 1px; left: 3px;
  font: 600 12px var(--fonte-mono); color: var(--panel); background: var(--red); padding: 0 4px; border-radius: var(--coin-s); white-space: nowrap; }
.wb-pl-dl small.sem { font-family: var(--fonte-mono); }


/* La vue « Mois » : une semaine par ligne, dimanche replié ; la case ouvre son jour. */
.wb-pl-vue-mois .wb-pl-entete, .wb-pl-sem { grid-template-columns: 56px repeat(6, minmax(0, 1fr)) 90px; }
.wb-pl-sem { display: grid; border-bottom: 1px solid var(--rule-2); background: var(--panel); }
.wb-pl-sem > .sn { font-family: var(--fonte-mono); font-size: 12.5px; color: var(--ink-3);
  padding: 6px 8px; border-right: 1px solid var(--rule); }
.wb-pl-mtitre { position: sticky; top: 27px; z-index: 4; background: var(--paper); padding: 6px 10px;
  font-weight: 700; font-size: 14.5px; border-bottom: 1px solid var(--rule-2); }
.wb-pl-jm { border-right: 1px solid var(--rule); padding: 5px 7px; min-height: 118px; min-width: 0;
  display: flex; flex-direction: column; gap: 4px; cursor: pointer; }
.wb-pl-jm:hover { background: var(--acc-w); }
.wb-pl-jm:focus-visible { outline: 2px solid var(--acc); outline-offset: -2px; }
.wb-pl-jm.auj { background: var(--repere-aujourdhui-w); }
.wb-pl-jm.passe { background: var(--paper); color: var(--ink-3); }
.wb-pl-jm.ferme { background: var(--trame-indispo); cursor: default; }
.wb-pl-jm .dt { display: flex; justify-content: space-between; align-items: baseline; gap: 4px; font-weight: 700; }
.wb-pl-jm .dt small { font-weight: 400; color: var(--ink-3); font-size: 12.5px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.wb-pl-pile { display: flex; height: 9px; border-radius: 3px; overflow: hidden; background: var(--rule); }
.wb-pl-pile span { display: block; height: 100%; background: var(--b); }
.wb-pl-jm .cpt { display: flex; flex-wrap: wrap; gap: 3px 8px; font-size: 12.5px; color: var(--ink-2); }
.wb-pl-jm .cpt b { color: var(--red); }
.wb-pl-jm .mecs { display: grid; gap: 2px; font-size: 12px; color: var(--ink-2); }
.wb-pl-jm .mecs > div { display: grid; grid-template-columns: minmax(0, 76px) 1fr 44px; gap: 5px; align-items: center; }
.wb-pl-jm .mecs span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-pl-jm .mecs em { font-style: normal; color: var(--ink-3); }
.wb-pl-jm .mecs .wb-pl-barre { margin: 0; }
.wb-pl-jm .mecs i { font-style: normal; text-align: right; font-variant-numeric: tabular-nums; }
.wb-pl-sem > .wb-pl-jm:last-child .mecs { display: none; }
.wb-pl-ty-gris { --f: var(--ty-gris-f); --b: var(--ty-gris-b); --a: var(--ty-gris-a); }
.wb-pl-ty-orange { --f: var(--ty-orange-f); --b: var(--ty-orange-b); --a: var(--ty-orange-a); }
.wb-pl-ty-rouge { --f: var(--ty-rouge-f); --b: var(--ty-rouge-b); --a: var(--ty-rouge-a); }
.wb-pl-ty-violet { --f: var(--ty-violet-f); --b: var(--ty-violet-b); --a: var(--ty-violet-a); }
.wb-pl-ty-bleu { --f: var(--ty-bleu-f); --b: var(--ty-bleu-b); --a: var(--ty-bleu-a); }
.wb-pl-ty-vert { --f: var(--ty-vert-f); --b: var(--ty-vert-b); --a: var(--ty-vert-a); }

.wb-maitre-detail > .wb-detail.wb-pl-volet { flex-basis: 360px; max-width: 360px; margin-left: 0;
  padding: 12px 14px; font-size: 13.5px; }
.wb-pl-volet h2 { font-size: 15px; font-weight: 700; margin: 0; }
.wb-pl-volet .sous { color: var(--ink-2); margin-bottom: 8px; }
.wb-pl-volet .fermer { float: right; border: 0; background: none; color: var(--ink-2); font-size: 18px; line-height: 1; }
.wb-pl-etapes { list-style: none; margin: 10px 0; padding: 0; }
.wb-pl-etapes li { display: flex; align-items: center; gap: 8px; padding: 2px 0; color: var(--ink-3); }
.wb-pl-etapes li.faite { color: var(--ink-2); }
.wb-pl-etapes li.courante { color: var(--ink); font-weight: 600; }
.wb-pl-volet dl { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: 3px 8px; margin: 10px 0; }
.wb-pl-volet dt { font-weight: 400; color: var(--ink-3); }
.wb-pl-volet dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.wb-pl-volet .actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.wb-pl-badges { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; }
.wb-pl-badges .wb-pl-ftype { cursor: default; height: 24px; }
.wb-pl-badge-rouge, .wb-pl-promis { display: inline-flex; align-items: center; height: 24px; padding: 0 8px;
  border-radius: 12px; font-size: 13px; font-weight: 600; }
.wb-pl-badge-rouge { background: var(--red); color: var(--panel); }
.wb-pl-promis { border: 1px solid var(--ink); color: var(--ink); }
.wb-pl-oubli { margin: 0 0 8px; padding: 6px 8px; border-left: 3px solid var(--red); background: var(--red-w); color: var(--red); font-weight: 600; }
.wb-pl-etapes small { margin-left: auto; color: var(--ink-3); font-weight: 400; font-size: 12.5px; text-align: right; }
.wb-pl-volet dd.att { color: var(--red); font-weight: 600; }
.wb-pl-travaux { border-top: 1px solid var(--rule); padding-top: 8px; display: grid; gap: 4px; }
.wb-pl-travaux > div { display: flex; justify-content: space-between; gap: 8px; }
.wb-pl-travaux > div > span:last-child { color: var(--ink-2); flex: none; }
.wb-pl-travaux .att { color: var(--red); }
.wb-pl-travaux .ok { color: var(--av-fini); }
.wb-pl-deplace { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; padding: 8px; background: var(--paper); border: 1px solid var(--rule); border-radius: var(--coin-s); }
.wb-pl-deplace .form-control, .wb-pl-deplace .form-select { width: auto; flex: 1 1 8rem; }
.wb-pl-rdv.bouge { animation: wb-pl-bouge 1.2s ease-out; }
@keyframes wb-pl-bouge { from { box-shadow: 0 0 0 4px var(--acc); } to { box-shadow: 0 0 0 0 transparent; } }
.wb-pl-urg { display: inline-block; padding: 0 4px; border-radius: var(--coin-s); background: var(--red); color: var(--panel); font-size: 12px; }
.wb-pl-pied .gestes { margin-left: auto; }
.wb-pl-pied kbd { font: 12px var(--fonte); border: 1px solid var(--rule-2); border-radius: var(--coin-s); padding: 0 4px; color: var(--ink-3); background: var(--paper); }
.wb-pl-avancee .pc { font-family: var(--fonte-mono); font-weight: 600; color: var(--ink); }
.wb-pl-avancee .maj { margin-left: auto; color: var(--ink-3); font-family: var(--fonte-mono); }
.wb-pl-annonce { position: fixed; left: 50%; bottom: 48px; transform: translateX(-50%); z-index: 1080; max-width: min(90vw, 44rem);
  background: var(--ink); color: var(--panel); padding: 7px 14px; border-radius: var(--coin); font-size: 14px; }
.wb-pl-annonce.alerte { background: var(--red); }

.wb-pl-pied { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; padding: 6px 12px;
  font-size: 12.5px; color: var(--ink-2); border: 1px solid var(--rule); border-top: 0; background: var(--panel); }
.wb-pl-pied span { display: inline-flex; align-items: center; gap: 5px; }

/* ══ L'ORDRE D'ATELIER A2 — trois colonnes dans l'ordre de la saisie ═════════
   Maquette `docs/charte/planning-a2.html`, écran « Nouvel ordre ». */
.wb-fiche-bornee.wb-fiche-bornee--trois { max-width: 112rem; }
.wb-no-cols { display: grid; grid-template-columns: minmax(18rem, 26rem) minmax(20rem, 1fr) minmax(30rem, 44rem);
  border: 1px solid var(--rule); background: var(--panel); }
.wb-no-col { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; min-width: 0;
  border-right: 1px solid var(--rule); }
.wb-no-col:last-child { border-right: 0; background: var(--paper); }
.wb-no-col > fieldset { margin-bottom: 0 !important; }
.wb-no-titre { font-size: 15px; font-weight: 700; margin: 0; display: flex; gap: 8px; align-items: center; }
.wb-no-titre span { width: 22px; height: 22px; border-radius: 50%; background: var(--ink); color: var(--panel);
  font-size: 12.5px; display: grid; place-items: center; }
.wb-no-coord { display: grid; grid-template-columns: 7rem minmax(0, 1fr); gap: 4px 8px; align-items: center; margin: 0 0 10px; font-size: 13.5px; }
.wb-no-coord dt { font-weight: 400; color: var(--ink-3); }
.wb-no-coord dd { margin: 0; min-width: 0; display: flex; align-items: center; gap: 6px; }
.wb-no-coord .vide { border-color: var(--etat-attention); background: var(--paper); }
.wb-no-coord .mod { color: var(--etat-ok); font-size: 12.5px; white-space: nowrap; }
.wb-no-npa input:first-child { flex: 0 0 5rem; }
.wb-no-coord .wb-sg { flex: 1 1 auto; min-width: 0; }
.wb-no-vhcs { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 6px; margin-bottom: 6px; }
.wb-no-vhc { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; padding: 6px 8px; text-align: left;
  border: 1px solid var(--rule-2); border-radius: var(--coin); background: var(--panel); color: var(--ink); font: inherit; font-size: 13px; }
.wb-no-vhc span { color: var(--ink-2); }
.wb-no-vhc small { color: var(--ink-3); font-size: 12px; }
.wb-no-vhc.on { border-color: var(--acc); box-shadow: 0 0 0 1px var(--acc); background: var(--acc-w); }
.wb-no-vhc:focus-visible { outline: 2px solid var(--acc); outline-offset: 1px; }
.wb-no-trav { width: 100%; border-collapse: collapse; font-size: 13.5px; margin-bottom: 6px; }
.wb-no-trav th { font-weight: 600; font-size: 12.5px; color: var(--ink-3); border-bottom: 1px solid var(--rule-2); padding: 3px 4px; text-align: left; }
.wb-no-trav td { border-bottom: 1px solid var(--rule); padding: 4px; vertical-align: top; }
.wb-no-trav th:nth-child(3) { width: 38%; }
.wb-no-trav td:last-child { width: 1.5rem; }
.wb-no-trav .t { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.wb-no-trav small { color: var(--ink-3); }
.wb-no-trav .stock { color: var(--etat-ok); }
.wb-no-trav .cmd { color: var(--red); }
.wb-no-trav .vide { color: var(--ink-3); }
.wb-no-trav .suppr { border: 0; background: none; color: var(--ink-3); line-height: 1; padding: 0 4px; }
.wb-no-trav .suppr:hover { color: var(--red); }
.wb-no-trav tr.act td { border-bottom: 0; padding: 1px 4px 1px 16px; font-size: 12.5px; color: var(--ink-2); }
.wb-no-trav tr.act td:last-child { padding-left: 4px; }
/* La liste des services et les cases d'options restent dans le formulaire, sous le tableau. */
.wb-no-source { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.wb-no-recap { margin-right: auto; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; font-size: 13.5px; color: var(--ink-2); min-width: 0; }
.wb-no-recap b { color: var(--ink); }
.wb-no-recap .quand { color: var(--ink); font-weight: 600; }
.wb-no-recap .opt { color: var(--red); font-weight: 600; }
.wb-no-types { display: flex; flex-wrap: wrap; gap: 5px; }
.wb-no-types .wb-pl-ftype { height: 30px; }
.wb-no-types .btn-check:checked + .wb-pl-ftype { box-shadow: 0 0 0 2px var(--ink); }
.wb-no-types .btn-check:focus-visible + .wb-pl-ftype { outline: 2px solid var(--acc); outline-offset: 2px; }
.wb-no-rang { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 13.5px; }
.wb-no-lib { color: var(--ink-3); }
.wb-no-mecano { width: auto; max-width: 16rem; }
.wb-no-nav { margin-left: auto; display: inline-flex; gap: 4px; }
.wb-no-semaine { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 6px; min-height: 8rem;
  font-variant-numeric: tabular-nums; }
.wb-no-semaine[aria-busy="true"] { opacity: .5; }
.wb-no-semaine > .rien { grid-column: 1 / -1; margin: 0; color: var(--ink-3); }
.wb-no-jour { background: var(--panel); border: 1px solid var(--rule-2); border-radius: 6px; display: flex;
  flex-direction: column; min-width: 0; }
.wb-no-jour > header { padding: 5px 7px; border-bottom: 1px solid var(--rule); }
.wb-no-jour > header b { font-size: 13.5px; display: block; }
.wb-no-jour > header small { color: var(--ink-3); font-size: 11.5px; display: block; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.wb-no-jour.auj > header { background: var(--repere-aujourdhui-w); }
.wb-no-jour .demi { padding: 5px; display: grid; gap: 3px; }
.wb-no-jour .demi .wb-no-cr[data-heure] { flex-wrap: nowrap; padding: 1px 6px; }
.wb-no-jour .demi > span { font-size: 12px; color: var(--ink-3); font-weight: 600; }
.wb-no-cr { font: inherit; font-size: 13px; border: 1px solid var(--rule-2); background: var(--panel); color: var(--ink);
  border-radius: 5px; padding: 3px 6px; text-align: left; cursor: pointer; display: flex; flex-wrap: wrap;
  justify-content: space-between; align-items: baseline; gap: 0 6px; line-height: 1.25; min-width: 0; }
.wb-no-cr small { color: var(--ink-3); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-no-cr strong { font-weight: 600; }
.wb-no-cr.on { background: var(--acc); border-color: var(--acc); color: var(--panel); }
.wb-no-cr.on small { color: var(--panel); }
.wb-no-cr:focus-visible { outline: 2px solid var(--acc); outline-offset: 1px; }
.wb-no-cr.sur { border-color: var(--etat-attention); border-style: dashed; }
.wb-no-cr.sur.on { background: var(--etat-attention); border-style: solid; }
.wb-no-jour .rien { font-size: 12.5px; color: var(--ink-3); }
.wb-no-detail { background: var(--panel); border: 1px solid var(--rule-2); border-radius: 6px; padding: 8px 10px;
  display: grid; gap: 4px; font-size: 13px; }
.wb-no-detail > header { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; }
.wb-no-detail > header .fl { color: var(--etat-attention); font-weight: 600; }
.wb-no-jl { display: grid; grid-template-columns: 84px minmax(0, 1fr) 72px; gap: 6px; align-items: center; }
.wb-no-jl .nm { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-no-jl .nm em { font-style: normal; font-weight: 400; color: var(--ink-3); }
.wb-no-jl .nm.placer { color: var(--red); }
.wb-no-jl .ch { text-align: right; color: var(--ink-2); font-size: 12px; }
.wb-no-jl .ch.trop { color: var(--red); font-weight: 600; }
.wb-no-jl .wb-pl-frise { min-height: 26px; border-radius: 3px; }
.wb-no-jl.heures .wb-pl-heures { height: 16px; font-size: 11.5px; color: var(--ink-3); }
.wb-no-jl.heures .wb-pl-heures span { position: absolute; top: 0; transform: translateX(-50%); }
.wb-no-jl .ap { display: flex; flex-wrap: wrap; gap: 4px; }
.wb-no-jb { font-size: 11.5px; line-height: 20px; padding: 0 4px; border-radius: 3px;
  border: 1px solid var(--b, var(--ty-gris-b)); background: var(--f, var(--ty-gris-f)); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.wb-pl-frise .wb-no-jb { position: absolute; top: 2px; bottom: 2px; z-index: 1; }
.wb-no-detail > footer { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 6px; font-size: 12.5px; color: var(--ink-3); }
.wb-no-detail > footer .wb-no-jb { width: 26px; height: 14px; line-height: 10px; padding: 0; }
.wb-no-jb.pause { background: var(--trame-indispo); border: 1px solid var(--rule); }
.wb-no-jb.nouveau { background: var(--acc-w); border: 2px dashed var(--acc); color: var(--acc); font-weight: 600; z-index: 2; }
@media (max-width: 1400px) {
  .wb-no-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .wb-no-creneau { grid-column: 1 / -1; border-top: 1px solid var(--rule); }
}
@media (max-width: 900px) {
  .wb-no-cols { grid-template-columns: minmax(0, 1fr); }
  .wb-no-col { border-right: 0; border-bottom: 1px solid var(--rule); }
  .wb-no-semaine { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.wb-trdv .rang { white-space: nowrap; }
.wb-trdv .wb-pl-ftype { cursor: default; max-width: 100%; }
.wb-trdv .wb-pl-ftype span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Aperçu de ligne (gabarit A, `App\Apercu`) : lire avant d'ouvrir ───── */
#wb-entity-qv-body.wb-qv-attente { opacity: .55; transition: opacity .1s .1s; }
.apercu { display: grid; gap: 14px; font-size: 14px; }
.apercu p { margin: 0; }
.apercu-sous { color: var(--ink-2); font-size: 13px; margin-top: -8px !important; }
.apercu-savoir { color: var(--etat-attention); background: var(--etat-attention-w); border-left: 3px solid var(--etat-attention);
  padding: 6px 9px; font-size: 13.5px; }
.apercu-du .et { font-size: 13.5px; padding: 2px 8px; }
.apercu-kv { display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 5px 10px; margin: 0; }
.apercu-kv dt { color: var(--ink-3); font-weight: 400; }
.apercu-kv dd { margin: 0; overflow-wrap: anywhere; }
.apercu-manque { color: var(--etat-attention); }
.apercu-vide { color: var(--ink-3); }
.apercu-bloc h3 { font-size: 12.5px; font-weight: 600; color: var(--ink-3); margin: 0 0 6px; }
.apercu-liste, .apercu-motos { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.apercu-liste li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0 8px; align-items: baseline; }
.apercu-liste li a, .apercu-motos li a { color: var(--ink); font-weight: 600; }
.apercu-liste small { grid-column: 1 / -1; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.apercu-etat { text-align: right; white-space: nowrap; }
.apercu-motos li { display: flex; justify-content: space-between; gap: 8px; border: 1px solid var(--rule);
  border-radius: var(--coin); padding: 6px 9px; }
.apercu-motos small { color: var(--ink-3); white-space: nowrap; }
.apercu-motos .apercu-reste { border: 0; padding: 0 9px; color: var(--ink-3); }
.apercu-actions { display: flex; flex-wrap: wrap; gap: 8px; border-top: 1px solid var(--rule); padding-top: 12px; }
.apercu-actions kbd, .dossier-actions .btn-primary kbd { padding: 0 4px; font-size: 11.5px; color: var(--ink-3); background: var(--paper);
  border: 1px solid var(--rule-2); }
.apercu-actions .btn-primary kbd, .dossier-actions .btn-primary kbd { background: transparent; color: inherit; border-color: currentColor; opacity: .75; }
.wb-dt-touches { margin: 8px 0 0; font-size: 12px; color: var(--ink-3); }
.wb-dt-touches kbd { padding: 0 4px; font-size: 11.5px; color: var(--ink-2); background: var(--paper); border: 1px solid var(--rule-2); }
.wb-dt-touches kbd:not(:first-child) { margin-left: 12px; }
.wb-dt-touches kbd:first-child + kbd { margin-left: 2px; }
.wb-dt-kbd { padding: 0 4px; font-size: 11px; color: var(--ink-3); background: var(--paper); border: 1px solid var(--rule-2); }

/* Dossier (gabarit B) : manchette, puis trois colonnes — joindre le client, ce qui
   est en cours, ce qui s'est passé. */
.dossier-manchette { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 12px 24px;
  padding: 14px 0 12px; border-bottom: 1px solid var(--rule); }
.dossier-titre { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin: 0 0 6px; font-size: 22px; font-weight: 600; }
.dossier-titre .apercu-du { margin: 0; font-size: 13px; font-weight: 400; }
.dossier-fait { font-size: 12.5px; color: var(--ink-3); }
/* La rangée d'étiquettes du dossier véhicule : libellé au-dessus de sa valeur, une colonne par donnée, jamais coupée. */
.dossier-rangee { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 6px 32px; margin: 0; }
.dossier-rangee dt { font-size: 11.5px; font-weight: 400; color: var(--ink-3); white-space: nowrap; }
.dossier-rangee dd { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 14px; white-space: nowrap; }
.dossier-rangee dd.fort a { font-weight: 600; }
.dossier-rangee a { text-decoration: none; }
.dossier-rangee .code { font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.dossier-rangee .vide, .dossier-rangee .teinte { color: var(--ink-3); }
.dossier-rangee .btn-link { font-size: 14px; line-height: inherit; border: 0; }
.dossier-rangee .rouge { color: var(--red); }
.dossier-rangee .wb-pastille { width: 12px; height: 12px; border-radius: 50%; border: 1px solid var(--rule-2); }
.dossier-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.dossier-corps { display: grid; grid-template-columns: minmax(290px, 360px) minmax(0, 1fr) minmax(0, 1.15fr); gap: 28px;
  align-items: start; margin-top: 14px; }
@media (max-width: 1280px) { .dossier-corps { grid-template-columns: minmax(280px, 340px) minmax(0, 1fr); } .dossier-droite { grid-column: 1 / -1; } }
@media (max-width: 760px) { .dossier-corps { grid-template-columns: minmax(0, 1fr); } }
.dossier-consigne { margin: 0 0 6px; font-size: 12px; color: var(--ink-3); }
.dossier-h2 { margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--ink-3); }
.dossier-centre { display: grid; gap: 22px; }
.dossier-centre .apercu-bloc h3 { font-size: 13px; }
.dossier-ajout { justify-self: start; margin-top: -14px; padding: 4px 0; border: 0; background: none; color: var(--acc); font-size: 13px; }
.dossier-section { margin-top: 10px; border-top: 1px solid var(--rule); padding-top: 8px; }
/* La section porte déjà le filet : le bloc de sa légende n'en ajoute pas un second. */
.dossier-section > summary > .wb-bloc { margin-top: 0; padding-top: 0; border-top: 0; }
/* La carte comptoir : le libellé à gauche, le champ à droite, « ✓ enregistré » dessous. */
.dossier-gauche .wb-fiche-champs > .row { --bs-gutter-y: 6px; }
.dossier-gauche .wb-fiche-champs .row > [class*="col-"] { flex: 0 0 100%; max-width: 100%; }
.dossier-gauche .wb-fiche-champs .row > div:has(> .form-label) { display: grid; grid-template-columns: 96px minmax(0, 1fr);
  column-gap: 10px; align-items: center; }
.dossier-gauche .wb-fiche-champs .row > div:has(> .form-label) > * { grid-column: 2; }
.dossier-gauche .wb-fiche-champs .row > div:has(> .form-label) > .form-label { grid-column: 1; grid-row: 1; margin: 0;
  font-size: 12.5px; color: var(--ink-3); }
.dossier-gauche .form-text { display: none; }
.dossier-gauche .mod { color: var(--etat-ok); font-size: 12px; }
.dossier-gauche [data-f].vide { border-style: dashed; }
.dossier-detenteur { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin-bottom: 14px; font-size: 13.5px; }
.dossier-detenteur > a { flex: 1 1 100%; font-size: 15px; font-weight: 600; }
.dossier-detenteur .apercu-du { margin: 0; }
.dossier-centre .wb-compteur { flex: none; }
.dossier-centre .wb-compteur .fig { font-size: 28px; font-weight: 600; }
/* Une zone de panneau qui n'a rien à dire part avec son titre (`pertinence()`). */
.dossier-centre > div:has(> .wb-bloc[hidden]) { display: none; }
/* La frise : une ligne par évènement, filtrable par nature (cases radio, sans script). */
.frise-filtres { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
.frise-filtre { padding: 2px 10px; border: 1px solid var(--rule-2); font-size: 12.5px; color: var(--ink-2); cursor: pointer; }
.btn-check:checked + .frise-filtre { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.btn-check:focus-visible + .frise-filtre { outline: 2px solid var(--acc); outline-offset: 1px; }
.frise-liste { list-style: none; margin: 0; padding: 0; }
.frise-liste li { display: grid; grid-template-columns: 64px minmax(0, 1fr) auto; gap: 0 12px; align-items: baseline;
  padding: 7px 0; border-bottom: 1px solid var(--rule); font-size: 13.5px; }
.frise-liste li.frise-annee { display: block; padding: 12px 0 4px; border: 0; font-size: 12px; font-weight: 600; color: var(--ink-3); }
.frise-liste time { color: var(--ink-3); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.frise-liste a, .frise-liste b { color: var(--ink); font-weight: 600; }
.frise-liste small { display: block; color: var(--ink-2); overflow-wrap: anywhere; }
.frise-etat { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.dossier-h2-ligne { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.dossier-h2-ligne .dossier-h2 { margin: 0; }
.frise-appel-form { display: grid; gap: 4px; margin-bottom: 12px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); }
.frise-appel-form[hidden] { display: none; }
.frise-appel-titre { margin: 0; font-size: 13.5px; font-weight: 600; }
/* Chaque ligne s'ouvre toute entière : le lien ou le bouton couvre la ligne. */
.frise-liste li { position: relative; }
.frise-liste li > div > a::after, .frise-appel::after { content: ""; position: absolute; inset: 0; }
.frise-liste li:has(> div > a, .frise-appel):hover, .frise-pli summary:hover { background: var(--paper); }
.frise-appel { all: unset; color: var(--ink); font-weight: 600; cursor: pointer; }
.frise-appel:focus-visible, .frise-pli summary:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }
.frise-liste li.frise-pli { display: block; padding: 0; }
.frise-pli summary { display: grid; grid-template-columns: 64px minmax(0, 1fr) auto; gap: 0 12px; align-items: baseline; padding: 7px 0; cursor: pointer; list-style: none; }
.frise-pli summary::-webkit-details-marker { display: none; }
.frise-pli p { margin: 0 0 8px 76px; color: var(--ink-2); overflow-wrap: anywhere; }
/* Pli « Archives » : pièces déposées (`js/ged-pieces.js`), puis documents émis. Les lignes reprennent la frise ;
   la ligne entière ouvre le PDF, les boutons passent au-dessus de son lien. */
.wb-ged-pli > summary .wb-bloc > .lbl { display: inline; }
.wb-ged-compte { margin-left: 8px; font-size: 12.5px; font-weight: 400; color: var(--ink-3); }
.wb-ged-titre { margin: 14px 0 6px; font-size: 12.5px; font-weight: 600; color: var(--ink-3); }
.wb-ged-pli section:first-child > .wb-ged-titre { margin-top: 6px; }
.wb-ged-meta { font-size: 12px; color: var(--ink-3); }
.wb-depot.wb-ged-depot { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2px 8px;
  padding: 12px 16px; border-radius: var(--coin); font-size: 13.5px; color: var(--ink-2); }
.wb-ged-depot .bi { font-size: 18px; color: var(--ink-3); }
.wb-ged-depot .wb-ged-meta { flex-basis: 100%; }
.wb-ged-pli--survol .wb-ged-depot, .wb-ged-pli--survol:not([open]) > summary { border-color: var(--acc); background: var(--acc-w); }
.frise-liste.wb-ged-liste li { grid-template-columns: 76px minmax(0, 1fr) auto; }
.wb-ged-actions { position: relative; z-index: 1; display: flex; align-items: center; gap: 4px; }
.wb-ged-actions .wb-ged-icone { padding: 0 6px; border-color: transparent; color: var(--ink-3); }
.wb-ged-actions .wb-ged-icone:hover, .wb-ged-actions .wb-ged-icone:focus-visible { border-color: var(--rule-2); color: var(--ink); }
.wb-ged-voir-retirees.btn-link { margin-top: 4px; color: var(--ink-3); font-size: 12.5px; text-decoration: none; }
.wb-ged-voir-retirees.btn-link:hover { color: var(--ink); }
.frise-liste .wb-ged-piece--retiree > time, .frise-liste .wb-ged-piece--retiree a, .wb-ged-piece--retiree small { color: var(--ink-3); }
.frise-liste .wb-ged-piece--neuve { background: var(--acc-w); }
.frise-liste li.wb-ged-piece--saisie { display: block; padding: 6px 0; }
.wb-ged-form .form-label { margin-bottom: 2px; font-size: 12px; }
.wb-ged-fichier { display: flex; align-items: baseline; gap: 6px; margin: 0 0 4px; font-size: 13.5px; }
.wb-ged-fichier-nom { font-weight: 600; overflow-wrap: anywhere; }
.frise:has(input[value="atelier"]:checked) li[data-famille]:not([data-famille="atelier"]),
.frise:has(input[value="ventes"]:checked) li[data-famille]:not([data-famille="ventes"]),
.frise:has(input[value="messages"]:checked) li[data-famille]:not([data-famille="messages"]),
.frise:has(input[value="atelier"]:checked) .frise-annee:not([data-familles~="atelier"]),
.frise:has(input[value="ventes"]:checked) .frise-annee:not([data-familles~="ventes"]),
.frise:has(input[value="messages"]:checked) .frise-annee:not([data-familles~="messages"]) { display: none; }

/* Document (gabarit C) : les lignes en pleine largeur, une colonne droite fixe
   (totaux, encaissement, suite) ; ses cartes deviennent des sections filetées.
   La largeur se mesure sur la colonne, pas sur la fenêtre : un volet ouvert à
   côté (40 rem) la réduit de moitié sur un portable. La colonne droite passe
   sous les lignes quand celles-ci n'ont plus 640 px ; l'en-tête s'empile sous 600 px. */
.document-corps { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; margin-top: 14px; }
.document-lignes { flex: 999 1 640px; min-width: 0; container: document-lignes / inline-size; }
.document-corps > .document-colonne { flex: 1 1 420px; min-width: 0; }
@container document-lignes (max-width: 600px) {
  .document-lignes [class*="col-md-"]:not(.col-6) { width: 100%; }
  .document-lignes .col-6[class*="col-md-"] { width: 50%; }
}
.document-colonne { position: sticky; top: 8px; max-height: calc(100vh - 16px); overflow: auto; padding-left: 20px; border-left: 1px solid var(--rule); }
.document-colonne > .card { border: 0; border-top: 1px solid var(--rule); border-radius: 0; background: none; margin-bottom: 0 !important; }
.document-colonne > .card:first-child { border-top: 0; }
.document-colonne > .card > .card-header { padding: 10px 0 4px; background: none; border: 0; }
.document-colonne > .card > .card-body { padding: 4px 0 12px; }
.document-tva { width: 100%; margin: 4px 0; font-size: 12.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.document-tva th { font-weight: 600; text-align: right; border-bottom: 1px solid var(--rule); }
.document-tva td { text-align: right; }
.document-tva :is(th, td):first-child { text-align: left; }
.document-types { display: grid; gap: 8px; margin: 12px 0 4px; }
.document-types-groupe { display: grid; grid-template-columns: 160px minmax(0, 1fr); gap: 10px; align-items: baseline; }
.document-types-liste { display: flex; flex-wrap: wrap; gap: 6px; }
.document-type { padding: 4px 12px; border: 1px solid var(--rule-2); border-radius: 999px; background: var(--paper); color: var(--ink); font-size: 13.5px; }
.document-type small { margin-left: 4px; font-size: 11px; font-weight: 600; color: var(--ink-3); }
.document-type[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.document-type[aria-pressed="true"] small { color: var(--rule-2); }
.document-type:focus-visible { outline: 2px solid var(--acc); outline-offset: 1px; }
.dossier-actions .dropdown-item { font-size: 13.5px; }
.wb-cmd-desc { display: flex; align-items: center; gap: 8px; }
.wb-cmd-stock { flex: none; font-size: 12px; color: var(--etat-ok); font-variant-numeric: tabular-nums; white-space: nowrap; }
.wb-cmd-forfait { display: flex; align-items: center; justify-content: flex-end; gap: 4px; margin: 2px 0 0; font-size: 12px; color: var(--ink-2); white-space: nowrap; }
.wb-cmd-stock--bas { color: var(--etat-attention); }
.wb-cmd-stock--zero { color: var(--red); }

/* Aujourd'hui : colonnes d'action à gauche, le geste de la ligne choisie à droite. */
.auj-chiffres { display: flex; flex-wrap: wrap; background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); margin-bottom: 14px; }
.auj-chiffres a { display: grid; gap: 2px; padding: 10px 22px; border-right: 1px solid var(--rule); color: var(--ink); text-decoration: none; }
.auj-chiffres a:last-child { border-right: 0; }
.auj-chiffres a:hover b, .auj-chiffres a:focus-visible b { text-decoration: underline; }
.auj-chiffres b { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.auj-chiffres b.amb { color: var(--etat-attention); }
.auj-chiffres b.rge { color: var(--red); }
.auj-chiffres span { font-size: 12.5px; color: var(--ink-3); }
.auj-corps { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 16px; align-items: start; }
.auj-colonnes { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; align-items: start; }
.auj-gauche { min-width: 0; }
.auj-col, .auj-rub { background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); min-width: 0; }
.auj-plus { display: block; }
.auj-col h2, .auj-rub h2 { display: flex; align-items: baseline; gap: 8px; margin: 0; padding: 9px 12px 7px; font-size: 14px; font-weight: 600; border-bottom: 1px solid var(--rule); }
.auj-col h2 b, .auj-rub h2 b { font-size: 12.5px; color: var(--ink-3); font-weight: 600; }
.auj-col h2 a, .auj-rub h2 a { margin-left: auto; font-size: 12.5px; font-weight: 400; color: var(--ink-3); text-decoration: none; }
.auj-col--soir { border-color: var(--ink-3); }
.auj-col--soir h2 { background: var(--ink); color: var(--panel); border-radius: var(--coin) var(--coin) 0 0; }
.auj-col--soir h2 b, .auj-col--soir h2 a { color: var(--rule-2); }
/* Pistes portées par la liste (subgrid) : la colonne heure prend la largeur de son plus long contenu (date comprise) sur toutes les lignes. */
.auj-col ul { list-style: none; margin: 0; padding: 4px 0; display: grid; grid-template-columns: minmax(50px, max-content) minmax(0, 1fr) auto; column-gap: 10px; }
.auj-col li { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; row-gap: 2px; align-items: center; padding: 6px 12px; cursor: pointer; border-left: 3px solid transparent; }
.auj-col--sans-h ul { grid-template-columns: minmax(0, 1fr) auto; }
.auj-col--sans-m ul { grid-template-columns: minmax(50px, max-content) minmax(0, 1fr); }
.auj-col--sans-h.auj-col--sans-m ul { grid-template-columns: minmax(0, 1fr); }
.auj-col--sans-h li .h, .auj-col--sans-m li .m { display: none; }
.auj-col li:hover { background: var(--paper); }
.auj-col li.sel { background: var(--acc-w); border-left-color: var(--acc); }
.auj-col li:focus-visible { outline: 2px solid var(--acc); outline-offset: -2px; }
.auj-col li.attend { box-shadow: inset 3px 0 0 var(--red); }
.auj-col li.attend .h { color: var(--red); }
.auj-col li.du .m { color: var(--etat-attention); }
.auj-col li.fait { opacity: .35; transition: opacity .3s; }
.auj-col li .h { font-size: 12.5px; font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.auj-col--soir li .h { font-size: 15px; color: var(--ink); }
.auj-col li .q { min-width: 0; }
.auj-col li .q b, .auj-col li .q small { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.auj-col li .q b { font-weight: 600; font-size: 13.5px; }
.auj-col li .q small { font-size: 12px; color: var(--ink-3); }
.auj-col li .m { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; text-align: right; }
.auj-plus { padding: 8px 12px; border-top: 1px solid var(--rule); font-size: 13px; color: var(--acc); text-decoration: none; }
.auj-plus:hover, .auj-plus:focus-visible { text-decoration: underline; }
.auj-vide { margin: 0; padding: 10px 12px; color: var(--ink-3); font-size: 13px; }
.auj-volet { position: sticky; top: 12px; display: grid; gap: 12px; background: var(--panel); border: 1px solid var(--rule-2); border-radius: var(--coin); padding: 14px; }
.auj-volet h2 { margin: 0; font-size: 17px; font-weight: 600; }
.auj-sous { margin: 2px 0 0; color: var(--ink-2); font-size: 13px; }
.auj-kv { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 4px 10px; margin: 0; font-size: 13.5px; }
.auj-kv dt { color: var(--ink-3); font-weight: 400; }
.auj-kv dd { margin: 0; }
.auj-gestes { display: grid; gap: 8px; border-top: 1px solid var(--rule); padding-top: 12px; }
.auj-gestes .btn-primary { min-height: 38px; }
.auj-fait { margin: 0; color: var(--etat-ok); font-weight: 600; }
/* Même largeur de piste que `.auj-colonnes` : à largeur égale, colonnes CSS et grille auto-fill tombent sur les mêmes bords. */
.auj-rubriques { column-width: 300px; column-gap: 14px; margin-top: 14px; }
.auj-rub { break-inside: avoid; margin-bottom: 14px; }
.auj-calme { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 14px 0 0; padding: 9px 12px; background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); font-size: 13px; color: var(--ink-3); }
.auj-calme b { color: var(--ink-2); font-weight: 600; }
.auj-rub-corps { padding: 10px 12px 12px; font-size: 13.5px; }
.auj-rub-corps > :last-child { margin-bottom: 0; }
@media (max-width: 991.98px) { .auj-corps { grid-template-columns: 1fr; } .auj-volet { position: static; } }
.auj-gestes .btn-primary kbd { background: transparent; color: inherit; border-color: currentColor; opacity: .75; }

/* ── Clôture de caisse : poste de travail (comptage, résultat, mouvements) ── */
.cl-tete .dossier-actions .form-control, .cl-tete .dossier-actions .form-select { width: auto; }
.cl-corps { display: grid; grid-template-columns: minmax(0, 460px) minmax(0, 1fr) minmax(0, 480px); background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); }
.cl-corps > section { display: grid; gap: 14px; align-content: start; padding: 18px 22px; min-width: 0; }
.cl-corps > section + section { border-left: 1px solid var(--rule); }
.cl-corps h2 { margin: 0; font-size: 14px; font-weight: 600; color: var(--ink-2); }
.cl-corps h2 small { font-weight: 400; color: var(--ink-3); }
.cl-coupures { display: grid; grid-template-columns: 70px 110px minmax(0, 1fr); gap: 6px 14px; align-items: center; }
.cl-coupures .v { text-align: right; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.cl-coupures .form-control { height: 34px; font-size: 16px; text-align: right; font-variant-numeric: tabular-nums; }
.cl-coupures .s { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.cl-coupures .cl-aide { font-weight: 400; color: var(--ink-3); font-size: 12.5px; }
.cl-sep { grid-column: 1 / -1; border-top: 1px solid var(--rule); margin: 2px 0; }
.cl-grand { display: grid; gap: 4px; padding: 14px 18px; border: 1px solid var(--rule); border-radius: var(--coin-l); }
.cl-grand span { font-size: 13px; color: var(--ink-2); }
.cl-grand b { font-size: 40px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.cl-ecart { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 14px 18px; border-radius: var(--coin-l); background: var(--wash); color: var(--ink-2); }
.cl-ecart span { font-size: 15px; font-weight: 600; }
.cl-ecart b { font-size: 28px; font-variant-numeric: tabular-nums; }
.cl-ecart.juste { background: var(--etat-ok-w); color: var(--etat-ok); }
.cl-ecart.manque { background: var(--etat-ecart-w); color: var(--etat-ecart); }
.cl-ecart.exc { background: var(--etat-attention-w); color: var(--etat-attention); }
.cl-motif { display: grid; gap: 4px; }
.cl-motif .form-label { margin: 0; font-size: 13px; color: var(--ink-2); }
.cl-cloturer { height: 44px; font-size: 15px; }
.cl-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.cl-table th { text-align: left; font-size: 12px; font-weight: 600; color: var(--ink-3); padding: 5px 8px; border-bottom: 1px solid var(--rule-2); }
.cl-table td { padding: 6px 8px; border-bottom: 1px solid var(--rule); }
.cl-table .r { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cl-table tfoot td { font-weight: 700; border-bottom: 0; }
.cl-table .cl-vide { color: var(--ink-3); border-bottom: 0; }
.cl-detail summary { cursor: pointer; font-size: 13px; color: var(--ink-2); }
@media (max-width: 1200px) {
  .cl-corps { grid-template-columns: minmax(0, 1fr); }
  .cl-corps > section + section { border-left: 0; border-top: 1px solid var(--rule); }
}

/* L'ordre en dossier : étapes en ligne sous le titre, alerte d'accord, carte comptoir, tables des travaux et pièces. */
.ordre-etapes { display: flex; flex-wrap: wrap; gap: 4px 0; list-style: none; margin: 8px 0 0; padding: 0; font-size: 12.5px; color: var(--ink-3); }
.ordre-etapes li { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.ordre-etapes li:not(:last-child)::after { content: ""; width: 24px; height: 2px; margin: 0 8px; background: var(--rule); }
.ordre-etapes li.faite { color: var(--ink-2); }
.ordre-etapes li.faite::after { background: var(--etat-ok); }
.ordre-etapes li.courante { color: var(--ink); font-weight: 600; }
.ordre-etapes small { color: var(--ink-3); font-weight: 400; }
.ordre-alerte { margin: 0; padding: 8px 12px; border-bottom: 1px solid var(--rule); background: var(--etat-attention-w); color: var(--ink); font-size: 13.5px; }
.ordre-alerte b { color: var(--etat-attention); }
/* Contrat de location en dossier : deux colonnes (qui, quoi, quand | montant, départ, retour, garantie). */
.loc-corps { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.loc-corps fieldset { min-width: 0; }
@media (max-width: 1100px) { .loc-corps { grid-template-columns: minmax(0, 1fr); } }
.ordre-carte dl, .ordre-solde { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 6px 10px; align-items: baseline; margin: 0 0 18px; font-size: 13.5px; }
.ordre-carte dt, .ordre-solde dt { font-weight: 400; color: var(--ink-3); }
.ordre-carte dd, .ordre-solde dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.ordre-carte .mod { color: var(--etat-ok); font-size: 12px; }
.ordre-carte [data-f].vide { border-style: dashed; }
.ordre-du { color: var(--etat-attention); }
.ordre-table { width: 100%; border-collapse: collapse; font-size: 13.5px; background: var(--panel); border: 1px solid var(--rule); }
.ordre-table th { padding: 6px 10px; border-bottom: 1px solid var(--rule-2); font-size: 12.5px; font-weight: 600; color: var(--ink-3); text-align: left; }
.ordre-table td { padding: 7px 10px; border-bottom: 1px solid var(--rule); vertical-align: top; }
.ordre-table td small { display: block; color: var(--ink-3); font-size: 12px; }
.ordre-table .r { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ordre-accord { display: grid; gap: 8px; padding: 12px 14px; border: 1px solid var(--etat-attention); border-radius: var(--coin); background: var(--panel); }
.ordre-accord h3 { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 12px; margin: 0; font-size: 15px; font-weight: 600; }
.ordre-accord h3 span { font-size: 12.5px; color: var(--etat-attention); }
.ordre-accord-travaux { display: flex; justify-content: space-between; gap: 12px; margin: 0; padding: 6px 0; border-block: 1px solid var(--rule); }
.ordre-accord-travaux b { font-variant-numeric: tabular-nums; }
.ordre-accord-ligne { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ordre-accord-ligne .lbl { width: 72px; font-size: 12.5px; font-weight: 600; color: var(--ink-3); }
.ordre-accord .form-label { margin: 0; font-size: 12.5px; color: var(--ink-3); }
.ordre-montant { max-width: 12rem; }
.ordre-histo li { grid-template-columns: 84px minmax(0, 1fr); }
.ordre-histo time small { display: block; }

/* Article en dossier : le stock en chiffres, les ventes en barres sur douze mois. */
.art-lieux { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.art-lieu { display: grid; gap: 2px; padding: 10px 12px; background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); }
.art-lieu b { font-size: 26px; line-height: 1.1; font-variant-numeric: tabular-nums; }
.art-lieu span { font-size: 12.5px; color: var(--ink-3); }
.art-motos { overflow-x: auto; }
.art-lieu.zero b { color: var(--red); }
.art-barres { display: flex; align-items: flex-end; gap: 4px; height: 72px; padding: 8px 10px 0; background: var(--panel); border: 1px solid var(--rule); border-bottom: 0; border-radius: var(--coin) var(--coin) 0 0; }
.art-barres i { flex: 1; min-height: 1px; background: var(--acc); border-radius: 2px 2px 0 0; }
.art-barres i:last-child { opacity: .55; }
.art-barres-leg { display: flex; justify-content: space-between; padding: 4px 10px; font-size: 12px; color: var(--ink-3); background: var(--panel); border: 1px solid var(--rule); border-top: 0; border-radius: 0 0 var(--coin) var(--coin); }

/* Postes à la douchette (réception, inventaire) : le scan et sa carte à gauche, les listes à droite. */
.rc-corps { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 560px); background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); }
.rc-scan { display: grid; gap: 14px; align-content: start; padding: 18px 22px; min-width: 0; }
.rc-cote { display: grid; align-content: start; border-left: 1px solid var(--rule); min-width: 0; }
.rc-douchette { display: flex; gap: 8px; align-items: stretch; }
.rc-qte { display: flex; gap: 4px; align-items: center; }
.rc-qte .form-control { width: 5.5rem; }
.rc-bloc + .rc-bloc { border-top: 1px solid var(--rule); }
.rc-bloc > header { display: flex; align-items: center; gap: 8px; padding: 10px 16px; }
.rc-bloc > header h2 { margin: 0 auto 0 0; font-size: 14px; font-weight: 600; color: var(--ink-2); }
.rc-bloc > header .form-control { width: 8rem; }
.rc-defile { max-height: 19rem; overflow-y: auto; }
.rc-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.rc-table th { padding: 5px 16px; font-size: 12px; font-weight: 600; color: var(--ink-3); text-align: left; border-bottom: 1px solid var(--rule-2); }
.rc-table td { padding: 6px 16px; border-bottom: 1px solid var(--rule); vertical-align: middle; }
.wb-rec-scaninput { --taille-champ: 1.6rem; height: 56px; font-weight: 600; }
.wb-inv-scaninput { --taille-champ: 1.6rem; height: 56px; font-weight: 600; }
.wb-rec-card, .wb-inv-card { display: grid; gap: 6px; padding: 20px 22px; min-height: 15rem; border: 2px solid var(--rule); border-radius: var(--coin-l); background: var(--etat-neutre-w); transition: background .15s, border-color .15s; }
.wb-rec-card--ok, .wb-inv-card--ok { background: var(--etat-ok-w);        border-color: var(--etat-ok); }
.wb-rec-card--warn, .wb-inv-card--warn { background: var(--etat-attention-w); border-color: var(--etat-attention); }
.wb-rec-card--err, .wb-inv-card--err { background: var(--etat-ecart-w);     border-color: var(--etat-ecart); }
.wb-rec-card-state, .wb-inv-card-state { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.wb-rec-card-desc, .wb-inv-card-desc { font-size: 2rem; font-weight: 700; line-height: 1.1; overflow-wrap: anywhere; }
.wb-rec-card-ref, .wb-inv-card-ref { font-size: 14px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.wb-rec-card-figs, .wb-inv-card-figs { display: flex; gap: 10px; flex-wrap: wrap; }
.wb-rec-fig, .wb-inv-fig { flex: 1 1 6rem; padding: 8px 14px; background: var(--panel); border: 1px solid var(--rule); border-radius: var(--coin); }
.wb-rec-fig-lbl, .wb-inv-fig-lbl { font-size: 12.5px; color: var(--ink-3); }
.wb-rec-fig-val, .wb-inv-fig-val { font-size: 2rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.wb-rec-dest       { margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--rule); }
.wb-rec-ordre      { color: var(--acc); }
.wb-rec-ordre .small { color: var(--ink-2); }
.wb-inv-sector { padding: 10px 16px; border: 1px solid var(--rule); border-radius: var(--coin); font-size: 1.8rem; font-weight: 700; text-align: center; }
.wb-inv-sector--none { background: var(--etat-neutre-w); color: var(--etat-neutre); font-size: 1.2rem; font-weight: 600; }
.wb-inv-sector--active { background: var(--acc); color: var(--paper); border-color: var(--acc); }
.wb-inv-lines { max-height: 26rem; overflow-y: auto; }
.wb-inv-ligne-qte { --taille-champ: 1.25rem; }
@media (prefers-reduced-motion: reduce) { .wb-rec-card, .wb-inv-card { transition: none; } }
@media (max-width: 1200px) {
  .rc-corps { grid-template-columns: minmax(0, 1fr); }
  .rc-cote { border-left: 0; border-top: 1px solid var(--rule); }
}

/* Écran SMS : les modèles en lignes à filet, le choisi en lavis (charte A2). */
.wb-sms-modeles { border: 1px solid var(--rule); border-radius: var(--coin); overflow: hidden; background: var(--panel); }
.wb-sms-modele { all: unset; box-sizing: border-box; display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 2px 8px;
  width: 100%; padding: 7px 10px; cursor: pointer; border-top: 1px solid var(--rule); }
.wb-sms-modele:first-child { border-top: 0; }
.wb-sms-modele:hover { background: var(--paper); }
.wb-sms-modele[aria-checked="true"] { background: var(--acc-w); }
.wb-sms-modele:focus-visible { outline: 2px solid var(--acc); outline-offset: -2px; }
.wb-sms-modele kbd { grid-row: 1 / 3; margin: 0; align-self: start; height: 20px; display: grid; place-items: center; padding: 0;
  font: 600 11.5px var(--fonte); color: var(--ink-3); background: none; border: 1px solid var(--rule-2); border-radius: var(--coin-s); }
.wb-sms-modele[aria-checked="true"] kbd { color: var(--acc); border-color: var(--acc); }
.wb-sms-modele b { font-weight: 600; font-size: 13.5px; }
.wb-sms-modele span { font-size: 12.5px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Choix exclusif en liste à filet (radios `.btn-check` + `.wb-choix`) : une ligne par choix, jamais de rangs inégaux. */
.wb-choix-liste { border: 1px solid var(--rule); border-radius: var(--coin); overflow: hidden; background: var(--panel); }
.wb-choix { display: block; margin: 0; padding: 7px 10px; font-size: 13.5px; cursor: pointer; border-top: 1px solid var(--rule); }
.wb-choix-liste > .btn-check:first-child + .wb-choix { border-top: 0; }
.wb-choix:hover { background: var(--paper); }
.btn-check:checked + .wb-choix { background: var(--acc-w); color: var(--acc); font-weight: 600; }
.btn-check:focus-visible + .wb-choix { outline: 2px solid var(--acc); outline-offset: -2px; }
.btn-check:disabled + .wb-choix { color: var(--ink-3); cursor: not-allowed; }

/* Compteur des champs SMS (`js/sms-texte.js`) : les caractères hors du jeu GSM. */
.wb-sms-compteur mark {
  padding: 0 .2em;
  border: 1px solid var(--etat-attention);
  border-radius: 2px;
  background: var(--etat-attention-w);
  color: var(--ink);
}

/* Ligne « À compléter » d'une fiche client (`js/client-manques.js`) : discrète, filet d'attention, champ sur place. */
.wb-manques { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; padding: 5px 10px; margin: 0 0 .75rem;
  border-left: 3px solid var(--etat-attention); border-radius: 0 var(--coin) var(--coin) 0; background: var(--panel); font-size: 13px; }
.wb-manques-titre { font-weight: 600; color: var(--ink-2); }
.wb-manque { display: inline-flex; align-items: center; gap: 6px; }
.wb-manque .lbl { margin: 0; }
.wb-manque .form-control, .wb-manque .form-select { width: auto; min-width: 11rem; }
.wb-manque .mod { color: var(--etat-ok); font-size: 12px; }
.wb-manque.fait > .lbl, .wb-manque.fait > .btn-link { color: var(--ink-3); }
.wb-manque.fait [data-sans] { display: none; }
.wb-manque-note { color: var(--ink-3); font-size: 12px; }
.wb-cliq-decision .wb-manques { margin: 0; padding: 0; border: 0; background: none; }
.wb-manques-pastille { cursor: pointer; }

/* Période partagée (`App\Periode`) : raccourcis puis « Du … au », largeur des dates fixée ici. */
.wb-periode { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.wb-periode input[type="date"] { width: 9.5rem; }

/* Raccourcis d'un onglet de liste (Non soldées, À recevoir…) : sous le bandeau de portée. */
.wb-dt-raccourcis { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 .5rem; }

/* Total des colonnes de montant sur tout le filtre, dans le pied de grille. */
.wb-table-somme { margin-left: 1.25rem; color: var(--ink); white-space: nowrap; }
.wb-table-somme b { font-weight: 600; }

/* Échéance passée d'une facture ouverte. */
.wb-echue { color: var(--red); font-weight: 600; }

/* Tableau écrit par `js/tableau.js` dans un cadre qui défile : la tête reste collée. */
.wb-tableau-defile { max-height: calc(100vh - 24rem); overflow-y: auto; }
.wb-tableau > tbody, .wb-tableau > tfoot { font-size: 13px; }
.wb-tableau > tfoot td { font-weight: 600; }
/* Ordre : l'étape et l'accord sur les pièces d'usure restent sur la ligne du titre ; le titre se coupe. */
.wb-ordre-page .dossier-titre { flex-wrap: nowrap; }
.wb-ordre-page .dossier-titre > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
