/* FICHIER GÉNÉRÉ — ne pas modifier ici.
   Bundle du socle CSS : rouzes-ui/tokens.css, rouzes-ui/themes/hub.css,
   puis socle-erp/css/{animations,composants,toasts,bulle,pastille,billet,facture-client,onglets,tableau-debit,frise-chargement,taches,impression}.css
   Régénérer : node socle-erp/sync.mjs <chemin de rouzes-ui> */

/* ==================== rouzes-ui/tokens.css ==================== */
/* ==========================================================================
   ROUZES UI — jetons de conception (design tokens)

   Valeurs reprises À L'IDENTIQUE de rouzes-devis et rouzes-planning, qui les
   portaient en double : aucun changement visuel n'est attendu de l'extraction.

   Deux couches :
     1. PRIMITIVES (--rz-*)   : la palette et les échelles brutes. Un module ne
                                les utilise jamais directement.
     2. SÉMANTIQUE (--color-*, --space-*, …) : ce que les composants consomment.
                                Un thème (cf. themes/) ne redéfinit QUE cette
                                couche — c'est ce qui permet au Hub de garder
                                son identité marine sans dupliquer le socle.

   Ajouter un jeton ici suppose qu'au moins deux modules en aient besoin.
   Un besoin d'un seul module reste dans son propre CSS.
   ========================================================================== */

:root {
  /* --- 1. PRIMITIVES ----------------------------------------------------- */
  --rz-bleu-500: #3B82F6;
  --rz-bleu-600: #2563EB;
  --rz-bleu-700: #1D4ED8;
  --rz-bleu-050: #F1F5FD;
  --rz-bleu-100: #E4ECFC;
  /* Fond bleu pâle des badges d'information. Ajouté le 04/09/2026 : il vivait
     en dur dans le planning et le devis. */
  --rz-bleu-150: #DBEAFE;

  --rz-ardoise-900: #0F172A;
  --rz-ardoise-500: #55627A;
  --rz-ardoise-050: #F8FAFC;
  /* Barreaux relevés le 04/09/2026 dans les feuilles de module, où ils étaient
     en dur : 100 et 200 servent de fond de ligne et de filet de tableau, 400 et
     450 de texte discret. 450 n'est PAS un doublon de 500 (#55627A) : il est
     plus clair, et les snapper l'un sur l'autre change l'écran — arbitrage
     visuel à part, pas au détour d'un renommage. */
  --rz-ardoise-100: #F1F5F9;
  --rz-ardoise-200: #E2E8F0;
  --rz-ardoise-400: #94A3B8;
  --rz-ardoise-450: #64748B;
  --rz-ardoise-700: #334155;
  --rz-blanc: #FFFFFF;

  /* #059669 tombait à 3,8:1 sur le blanc des cartes : le message qui confirme
     une inscription ou un envoi de lien était le moins lisible de l'écran de
     connexion. #047857 est la même famille et donne 5,4:1 sur blanc, 4,8:1
     sur --color-ok-bg. Assombrir n'abîme rien dans l'autre sens : les emplois
     en FOND (texte blanc dessus) y gagnent aussi. */
  --rz-vert-600: #047857;
  --rz-vert-050: #D1FAE5;
  --rz-ambre-700: #B45309;
  --rz-rouge-600: #DC2626;
  --rz-rouge-050: #FEE2E2;

  /* --- Familles de STATUT complètes (04/09/2026) -------------------------
     Chaque statut se peint en badge par un TRIPLET fond / bordure / texte, et
     ces trois valeurs vivaient en dur dans quatre à six modules chacune. Le
     texte du triplet est plus FONCÉ que le `--color-*` autonome de la même
     famille (ambre-800 contre ambre-700) : il se lit sur un fond pâle, pas sur
     du blanc, et c'est le contraste qui l'exige. Les deux ne sont donc pas
     redondants — ils n'ont pas le même fond sous eux. */
  --rz-ambre-025: #FFFBEB;
  --rz-ambre-050: #FEF3C7;
  --rz-ambre-100: #FDE68A;
  --rz-ambre-800: #92400E;
  --rz-vert-100: #A7F3D0;
  --rz-vert-800: #065F46;
  --rz-rouge-700: #B42318;
  --rz-cyan-700: #0C6580;   /* 5,4:1 sur --color-info-bg (#0E7490 : 4,39:1, CSA-08) */

  /* --- 2. SÉMANTIQUE : couleurs ------------------------------------------ */
  --color-primary: var(--rz-bleu-600);
  --color-primary-hover: var(--rz-bleu-700);
  --color-on-primary: var(--rz-blanc);
  /* `--color-secondary` et `--color-accent` ont été renommés : shadcn emploie
     ces deux mots pour de tout autres rôles (bouton discret, fond de survol),
     et un même nom portant deux couleurs selon qu'on lit la variable ou
     l'utilitaire est un piège qui ne lève aucune erreur.
     Migration : dans rouzes-planning, --color-secondary → --color-hover-border
     (1 usage) et --color-accent → --color-success (15 usages). */
  --color-hover-border: var(--rz-bleu-500);
  --color-success: var(--rz-vert-600);
  --color-background: var(--rz-ardoise-050);
  --color-surface: var(--rz-blanc);
  --color-foreground: var(--rz-ardoise-900);
  --color-muted-fg: var(--rz-ardoise-500);
  /* Le texte MOYEN : plus present que --color-muted-fg, moins que
     --color-foreground. Trois emplois, deja identiques avant d'etre
     nommes — le survol d'une poignee de section, celui d'une poignee de
     colonne, et l'accueil de la barre du haut. */
  --color-foreground-soft: var(--rz-ardoise-700);
  --color-muted: var(--rz-bleu-050);
  --color-border: var(--rz-bleu-100);
  --color-destructive: var(--rz-rouge-600);
  --color-warning: var(--rz-ambre-700);
  --color-danger-bg: var(--rz-rouge-050);
  --color-ok-bg: var(--rz-vert-050);

  /* Triplets de statut : fond, bordure, texte. Le `-fg` se lit SUR le `-bg`
     de sa famille ; `--color-warning`, `--color-success` et
     `--color-destructive` restent le texte d'alerte sur fond neutre. */
  --color-warn-bg-soft: var(--rz-ambre-025);
  --color-warn-bg: var(--rz-ambre-050);
  --color-warn-border: var(--rz-ambre-100);
  --color-warn-fg: var(--rz-ambre-800);
  --color-ok-bg: var(--rz-vert-050);       /* fond de la pilule « ok » (socle + admin, 15/09) */
  --color-ok-border: var(--rz-vert-100);
  --color-ok-fg: var(--rz-vert-800);
  --color-danger-fg: var(--rz-rouge-700);
  --color-info-bg: var(--rz-bleu-150);
  --color-info-fg: var(--rz-cyan-700);
  --color-ring: var(--rz-bleu-600);

  /* --- 2. SÉMANTIQUE : espacements, rayons, typographie ------------------- */
  --space-05: 2px;
  --space-1: 4px;
  --space-15: 6px;
  --space-2: 8px;
  --space-25: 10px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* Pas de couche primitive ici : `themes/*.css` redéfinit `--radius-md/lg`
     EN DIRECT, donc `--rz-radius-*` n'était jamais le point de bascule —
     seulement un troisième nom pour la même valeur. */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 10px;
  --radius-xs: 3px;    /* filets, coins de cellule ; 3px vivait en dur 13 fois dans 4 modules (15/09) */
  --radius-xl: 16px;   /* grandes cartes (hub 18 → 16, chantiers 14 → 16). Les thèmes
                          atelier et hub redéfinissent md/lg à 8/12, À L'IDENTIQUE — un banc
                          l'exige : le hub et les modules partagent la même échelle rendue. */
  /* La PILULE. Elle s'écrivait `999px` (60 fois) et `99px` (11 fois) : deux
     valeurs pour une seule intention, dont aucune ne se cherche. */
  --radius-full: 999px;

  --font-body: "Fira Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Fira Code", ui-monospace, "Cascadia Code", monospace;
  --font-titres: "Archivo", var(--font-body);   /* remonté des deux thèmes, qui le portaient à l'identique (15/09) */

  /* --- 2. SÉMANTIQUE : échelle typographique -----------------------------
     Relevée dans le socle, où elle vivait en dur à 28 endroits. Entièrement
     en PIXELS, comme le reste : aucune valeur du socle n'est en `rem`, ce qui
     rend les composants insensibles au corps de texte du document. La nommer
     est la condition pour qu'un composant tiers (shadcn) s'y aligne. */
  --fs-3xs: 10px;      /* etiquettes de pastille, heures d'atelier, cases du Gantt */
  --fs-2xs: 11px;      /* étiquettes de menu, badges */
  --fs-xs: 12px;       /* légendes, aides de champ */
  --fs-sm: 13px;       /* texte secondaire */
  --fs-control: 12px;  /* boutons, champs, sélecteurs — la taille des CONTRÔLES.
                          12 px et non 13,5 depuis le 23/09/2026 (Cyril :
                          « toute l'app doit avoir la même taille de police,
                          la même que les pilules Grouper/urgence »). C'est
                          `--fs-xs`, la taille des pilules de Statistiques
                          prises pour modèle. Le palier tactile sous 640 px
                          reste à 16 px, plus bas : 12 px sous le doigt serait
                          illisible, et c'est une autre question. */
  --fs-body: 15px;     /* corps de l'interface */
  --fs-md: 16px;       /* titres de modale, marque */
  --fs-lg: 17px;       /* titres d'état vide */
  --fs-xl: 20px;       /* valeurs de KPI */
  /* Quatre tailles restent en dur dans base.css — 11,5 · 12,5 · 14 · 18 px,
     chacune sur un seul usage (aide de champ, libellé, accroche et marque de
     l'écran de connexion). Elles ne sont PAS promues en jetons : une échelle
     à seize barreaux ne se tient plus. Ce sont des candidates à la
     consolidation vers un barreau voisin, à trancher visuellement. */

  /* --- 2. SÉMANTIQUE : métriques des contrôles ---------------------------
     Un bouton, un champ et un sélecteur doivent pouvoir s'aligner au pixel
     sur une même barre d'actions, quelle que soit leur origine. */
  --control-h: 36px;
  --control-h-compact: 28px;  /* .btn-compact et .chip — la décision « 36 action / 28 compact » du 02/09 */
  --control-h-icone: 30px;    /* .btn-ico et les boutons de ligne (chantiers .btn-ligne) */
  --badge-h: 20px;            /* la pilule d'état : une hauteur fixe, pas un padding (15/09) */

  --shadow-menu: 0 4px 16px rgba(15, 23, 42, 0.12);
  /* Trois ombres, FIXÉES par conception le 15/09 (le 04/09 : aucune valeur
     partagée par deux modules, donc la règle « ≥ 2 modules » ne pouvait rien
     élire). Une ombre existante rejoint la plus proche par son flou :
     < 6 px → sm, 6-24 → md, > 24 → lg. */
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08);
  --shadow-md: var(--shadow-menu);
  --shadow-lg: 0 18px 50px rgba(15, 23, 42, 0.22);
  /* Voiles : fond de modale (::backdrop), arrière-plan de menu */
  --overlay-fort: rgba(15, 23, 42, 0.35);
  --overlay-doux: rgba(15, 23, 42, 0.22);
  /* Paliers d'empilement — 20 valeurs de -1 à 200 vivaient en dur */
  --z-dessous: -1;
  --z-sticky: 20;
  --z-menu: 40;
  --z-modal: 60;
  --z-toast: 100;
  --z-sommet: 200;
  --transition-fast: 150ms ease-out;
  --topbar-h: 56px;
  /* Gabarit de page : le pied de main.page, identique dans les six modules
     statiques (80 px, 64 au doigt) — remonté au socle le 15/09. */
  --page-pied: 80px;
  --page-pied-tactile: 64px;
}

/* --- 3. AU DOIGT -----------------------------------------------------------
   Mesuré le 27/08/2026 sur les neuf modules : `--control-h: 36px` mettait
   CHAQUE bouton de l'ERP sous les 44 px recommandés au doigt, et les champs à
   13,5 px déclenchaient le zoom automatique de Safari iOS — sous 16 px, iOS
   agrandit la page au focus d'un champ et ne la réduit jamais ensuite.

   Le seuil est déclenché par la LARGEUR, pas par `pointer: coarse` : sur la
   Surface Pro, `pointer: coarse` et `hover: none` répondent « tactile » en
   permanence, même au clavier et à la souris. La largeur, elle, ne ment pas.

   Ces deux jetons suffisent : `.btn`, `.champ` et les sélecteurs du socle les
   lisent déjà, donc la remontée est automatique partout. */
@media (max-width: 640px) {
  :root {
    --control-h: 44px;
    --fs-control: 16px;
  }
}

/* Palier commun des CHAMPS DE SAISIE (audit 01/10/2026 : CSA-02/03/13,
   CSB-08/09/10/11, CMZ-09). --fs-control ne suffit pas : une trentaine de
   règles de modules figent 11-15 px en dur, avec une spécificité allant
   jusqu'à (0,3,1) ou un #id (commandes.css : #pMotif, #pChantier…). Une règle
   basse, même chargée en dernier, ne les bat pas ; !important est ici le
   seul moyen d'un palier UNIQUE — il ramène TOUS les champs à 16 px,
   sous 640 px et sur iOS quelle que soit la largeur (iPhone en paysage, iPad :
   @supports -webkit-touch-callout, que la Surface Pro ne reconnaît pas), et
   laisse cases, curseurs, fichiers et boutons intacts. */
@media (max-width: 640px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]),
  select,
  textarea {
    font-size: 16px !important;
  }
}
@supports (-webkit-touch-callout: none) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]),
  select,
  textarea {
    font-size: 16px !important;
  }
}

/* ==================== rouzes-ui/themes/hub.css ==================== */
/* ==========================================================================
   THÈME HUB — identité marine du portail.

   Le Hub n'est pas un module de travail : c'est le vestibule de l'ERP. Il
   assume une identité plus marquée (marine / azur, titres Archivo, rayons de
   16 px) là où les modules restent volontairement neutres pour se faire
   oublier derrière la donnée.

   Ce fichier ne redéfinit QUE la couche sémantique : les composants du socle
   (.btn, .input, .modal, .toast…) déposés dans le Hub prennent l'identité du
   portail sans qu'une seule règle ne soit dupliquée.

   À importer APRÈS index.css :
     @import "rouzes-ui/index.css";
     @import "rouzes-ui/themes/hub.css";
   ========================================================================== */

:root {
  /* Primitives propres au Hub */
  --rz-marine: #001D57;
  --rz-marine-2: #032169;
  --rz-abysse: #020E2E;
  --rz-azur: #008CF8;
  --rz-azur-fonce: #0066D6;
  --rz-ciel: #7CC4FF;
  --rz-papier: #F4F7FC;
  --rz-encre: #0E1B3A;
  --rz-sourdine: #5A6B8C;
  --rz-ligne: #E0E8F5;

  /* Remappage de la couche sémantique */
  --color-primary: var(--rz-azur);
  --color-primary-hover: var(--rz-azur-fonce);
  --color-hover-border: var(--rz-ciel);
  --color-background: var(--rz-papier);
  --color-foreground: var(--rz-encre);
  --color-muted-fg: var(--rz-sourdine);
  --color-border: var(--rz-ligne);
  --color-ring: var(--rz-azur);


  /* Même échelle rendue que le thème Atelier (8/12) : un banc compare les deux
     thèmes. Les cartes du portail passent par --radius-xl (16). */
  --radius-md: 8px;
  --radius-lg: 12px;
  /* --font-titres vit dans tokens.css depuis le 15/09/2026 */

  --shadow-menu: 0 14px 34px rgba(0, 29, 87, 0.14);
}

/* ==================== socle-erp/css/animations.css ==================== */
/* Socle ERP — mouvement, chargement et focus.
 *
 * SOURCE UNIQUE recopiée par `node socle-erp/sync.mjs`. À importer APRÈS
 * tokens.css dans chaque module.
 *
 * Le vocabulaire d'animation n'existait que dans le hub : les modules de
 * travail étaient figés (un seul jeton de transition, bascules de vue
 * sèches, « Chargement… » en texte brut, aucun focus visible). Calibrage
 * volontairement sobre, calé sur la retenue du planning — pas une
 * surenchère d'effets.
 */

:root {
  --dur-1: 90ms;
  --dur-2: 150ms;
  --dur-3: 220ms;
  --dur-4: 320ms;
  --ease-sortie: cubic-bezier(.4, 0, .2, 1);
  --ease-ressort: cubic-bezier(.34, 1.4, .5, 1);
  --pas-echelon: 40ms;
}

/* ── Entrée de vue ───────────────────────────────────────────────────── */
@keyframes vue-entre { from { opacity: 0; transform: translateY(8px); } }
.anime-vue { animation: vue-entre var(--dur-3) var(--ease-sortie) both; }

/* Apparition échelonnée d'une liste : poser --i sur chaque enfant.
   L'index est plafonné à l'usage (au-delà de ~12, l'attente se voit). */
@keyframes apparait { from { opacity: 0; transform: translateY(6px); } }
.liste-echelonnee > * {
  animation: apparait var(--dur-3) var(--ease-sortie) both;
  animation-delay: calc(var(--i, 0) * var(--pas-echelon));
}

/* ── Survol de carte ─────────────────────────────────────────────────── */
.survol-carte {
  transition: transform var(--dur-2) var(--ease-sortie),
              box-shadow var(--dur-2) var(--ease-sortie),
              border-color var(--dur-2) var(--ease-sortie);
}
.survol-carte:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-menu, 0 6px 18px rgba(15, 23, 42, .12));
  border-color: var(--color-hover-border, var(--color-primary));
}

/* ── Chargement ──────────────────────────────────────────────────────── */
@keyframes squelette-onde { to { background-position: -200% 0; } }
.squelette {
  background: linear-gradient(90deg,
    var(--color-muted) 25%, var(--color-border) 37%, var(--color-muted) 63%);
  background-size: 280% 100%;
  animation: squelette-onde 1.4s linear infinite;
  border-radius: var(--radius-sm, 6px);
  height: 14px;
  margin: var(--space-2) 0;
}
.squelette-bloc {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md, 10px);
  padding: var(--space-4, var(--space-4));
  margin-bottom: var(--space-3, var(--space-3));
}
/* `long` manquait alors que repartition.js l'emploie : la barre gardait la
   largeur pleine par défaut, ce qui marchait par accident. Nommée pour que
   les trois largeurs se lisent ensemble. */
.squelette.long  { width: 100%; }
.squelette.court { width: 40%; }
.squelette.moyen { width: 65%; }

@keyframes tourner { to { transform: rotate(360deg); } }
.tourne { animation: tourner 1.6s linear infinite; }

/* ── État vide ───────────────────────────────────────────────────────── */
.etat-vide {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-2, var(--space-2));
  padding: 40px var(--space-4, var(--space-4));
  text-align: center;
  color: var(--color-muted-fg);
  animation: vue-entre var(--dur-3) var(--ease-sortie) both;
}
.etat-vide .ico { width: 32px; height: 32px; opacity: .45; }
.etat-vide strong { color: var(--color-foreground); font-size: var(--fs-sm); }
.etat-vide p { margin: 0; font-size: var(--fs-xs); max-width: 42ch; line-height: 1.6; }

/* ── Focus clavier, partout ──────────────────────────────────────────── */
:where(button, a, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--color-ring, var(--color-primary));
  outline-offset: 2px;
  border-radius: var(--radius-sm, 6px);
}

/* ── Détails dépliants ───────────────────────────────────────────────── */
details[open] > *:not(summary) {
  animation: vue-entre var(--dur-2) var(--ease-sortie) both;
}

/* ── Mouvement réduit ────────────────────────────────────────────────── */
/* Forme douce (0.01 ms) plutôt que `animation: none` : le spinner et les
   squelettes doivent rester compréhensibles, seul le mouvement s'efface. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ─── MODULE EMBARQUÉ DANS LE HUB ─────────────────────────────────────────
   Le portail coiffe l'iframe de sa propre barre — nom du module, agence, date,
   ouvrir-en-onglet. Le module y ajoutait la sienne : deux bandeaux empilés qui
   se répétaient le nom et l'agence, sur toutes les pages de tous les modules.

   La coquille garde la main, le module s'efface. Il ne s'efface QUE là :
   ouvert en direct, par son URL propre ou par « ouvrir dans un onglet », il
   reste seul à l'écran et son en-tête redevient sa seule identité.

   La classe est posée par socle/ui.js, qui teste `self !== top` — voir le
   commentaire là-bas pour pourquoi la détection vit dans le JS et la règle
   ici. L'identité « email · rôle » n'est pas perdue : elle vit dans le menu
   du compte du hub, en bas du dock. Une coquille n'a pas à la répéter sur
   chaque page. */
html.embarque .entete{display:none}

/* ==================== socle-erp/css/composants.css ==================== */
/* Socle ERP — le vocabulaire de composants des modules statiques.
 *
 * SOURCE UNIQUE recopiée par `node socle-erp/sync.mjs` dans
 * <module>/css/socle/composants.css. À charger APRÈS tokens.css et
 * theme-hub.css, AVANT la feuille du module — qui garde ainsi le dernier
 * mot sur tout ce qu'elle veut surcharger.
 *
 * POURQUOI ICI PLUTÔT QUE DANS rouzes-ui
 *
 * rouzes-ui/base.css porte déjà des composants, mais il ouvre par un reset
 * d'app-shell (`body { height: 100dvh; overflow: hidden }`) qui convient à
 * devis et planning et casse le défilement des modules statiques. Il leur
 * est donc inimportable — et les cinq modules ont réécrit le même
 * vocabulaire chacun de leur côté, en français, à l'octet près.
 *
 * CE QUI EST ICI, ET CE QUI N'Y EST PAS
 *
 * Uniquement les règles STRICTEMENT identiques dans tous les modules qui
 * les portent, et dont l'arrivée ne change le rendu d'aucun de ceux qui ne
 * les portaient pas (vérifié classe par classe dans leur balisage). Le
 * reste est resté local, et ce n'est pas un oubli :
 *
 *   .page             admin est à 1000px, les autres à 1180px
 *   .toast            TROIS variantes (admin empile, biblio et le reste
 *                     divergent) — un même nom pour trois composants
 *   .badge            admin en mono 10,5px, les autres en pilule
 *   .ico-marque       la teinte du module, par définition
 *
 * Ces quatre-là sont de vraies divergences visuelles : les réunir demande de
 * trancher une apparence, pas de déplacer du texte. Elles ne sont pas
 * réglées ici pour que ce fichier soit un déplacement à rendu identique.
 *
 * .carte-connexion y est depuis le 03/10/2026 : le bloc du trio
 * admin/chantiers/commandes, que Facturation avait repris à l'identique.
 * Biblio garde chez lui sa largeur de 440px, qui le surcharge.
 *
 * .pastille et .attribution méritent une note à part : le socle les PRODUIT
 * (pastille.js), mais seuls trois modules les stylent. Elles restent
 * locales tant que personne n'a vérifié à quoi elles ressemblent dans les
 * deux autres.
 */

/* ── Remise à zéro et page ───────────────────────────────────────────── */
* { box-sizing: border-box; }
html, body { margin: 0; }
body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--color-foreground);
  background: var(--color-background);
  min-height: 100vh;
}
[hidden] { display: none !important; }
.sprite { position: absolute; }

/* ── Barres de défilement ────────────────────────────────────────────
 *
 * Un pouce en pilule, sans boutons fléchés, INVISIBLE AU REPOS : il n'apparaît
 * qu'au survol du bloc qui défile (façon macOS). Sa teinte vient de
 * --color-foreground, donc la barre suit le thème du module (ardoise ailleurs,
 * sable à l'atelier) sans qu'aucun thème la redéclare.
 *
 * Rien ne bouge quand le pouce apparaît : la gouttière est réservée en
 * permanence, seule la couleur du pouce change.
 *
 * PIÈGE — `scrollbar-width` BAT `::-webkit-scrollbar`. Depuis Chrome 121 les
 * deux propriétés standard sont reconnues, et dès qu'elles sont posées le
 * moteur IGNORE tout le bloc ::-webkit-* : on retombe sur la barre native
 * fine, pouce carré. Elles sont donc réservées à Firefox, seul à ne pas
 * comprendre le sélecteur ::-webkit-scrollbar. Ne jamais les sortir de ce
 * @supports « en passant » : la pilule disparaîtrait sans la moindre erreur.
 *
 * ORDRE DES RÈGLES, ET NON SPÉCIFICITÉ : `:hover::-webkit-scrollbar-thumb`
 * (survol du bloc) et `::-webkit-scrollbar-thumb:hover` (survol du pouce
 * lui-même) pèsent le MÊME poids. C'est la dernière écrite qui gagne — d'où
 * l'ordre repos → bloc survolé → pouce survolé → pouce saisi. Les intervertir
 * ferait un pouce qui ne réagit plus sous le curseur.
 *
 * `background-clip: content-box` + bordure transparente amincissent le pouce
 * SANS changer la largeur de la gouttière.
 *
 * LONGHAND `background-color`, et non le raccourci `background` : le
 * raccourci remettrait background-clip à border-box, et le pouce reprendrait
 * toute la gouttière.
 *
 * Les rares zones qui masquent leur barre (`.rail-liste`, `.fiche-onglets`)
 * gardent le dernier mot : leur classe est plus spécifique, et leur feuille
 * se charge après celle-ci. */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: transparent transparent; }
  *:hover,
  *[data-defile] {
    scrollbar-color: color-mix(in srgb, var(--color-foreground) 28%, transparent)
                     transparent;
  }
}
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner { background-color: transparent; }
/* Pas de boutons fléchés aux extrémités : la barre n'est qu'un pouce. */
::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
::-webkit-scrollbar-thumb {
  background-color: transparent;
  background-clip: content-box;
  border: 3px solid transparent;
  border-radius: var(--radius-full);
  transition: background-color .2s ease;
}
:hover::-webkit-scrollbar-thumb,
[data-defile]::-webkit-scrollbar-thumb {
  background-color: color-mix(in srgb, var(--color-foreground) 24%, transparent);
}
::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--color-foreground) 42%, transparent);
}
::-webkit-scrollbar-thumb:active {
  background-color: color-mix(in srgb, var(--color-primary) 60%, transparent);
}

/* ── Icônes du sprite ────────────────────────────────────────────────── */
.ico {
  width: 20px; height: 20px; flex: none;
  fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}

/* ── En-tête de module ───────────────────────────────────────────────── */
/* ── En-tête ─────────────────────────────────────────────────────────── */
.entete {
  height: var(--topbar-h);
  display: flex; align-items: center; gap: var(--space-4);
  padding: 0 var(--space-5);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  position: sticky; top: 0; z-index: var(--z-sticky);
}
/* La marque est le <h1> de la page depuis le 15/09/2026 : margin 0 neutralise
   la marge que le navigateur donne à un h1, le reste est inchangé. */
.marque {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0;
  font-family: var(--font-titres);
  font-weight: 700; font-size: var(--fs-md);
}
.entete-note { margin-left: auto; color: var(--color-muted-fg); font-size: var(--fs-xs); }
.entete-agence { margin-left: var(--space-3); }
.puce-agence {
  display: inline-flex; align-items: center;
  font-weight: 500; font-size: var(--fs-2xs); font-family: var(--font-mono, monospace);
  line-height: normal; /* le raccourci font: remettait line-height a normal */
  padding: var(--space-1) var(--space-25); border-radius: var(--radius-full);
  color: var(--color-muted-fg); background: var(--color-muted);
  border: 1px solid var(--color-border);
}

/* ── Boutons et liens ────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  min-height: var(--control-h); padding: var(--space-15) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-full); /* pilule, comme les chips et les badges — décision Cyril, 02/09/2026 */
  font: 600 var(--fs-control) var(--font-body);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.btn .ico { width: 17px; height: 17px; }
/* Un bouton désactivé doit se VOIR : sans cette règle, #btnImprimerDalem
   (débit, coloris non déclaré) restait identique à un bouton actif et
   passait pour cassé — le title n'apparaît qu'au survol, jamais au doigt.

   `pointer-events: auto` est VOULU sur un vrai <button> : le navigateur bloque
   déjà le clic, et laisser passer le survol est la seule façon de montrer le
   title qui explique POURQUOI le bouton est éteint. Sur un élément qui n'est
   pas un bouton, rien ne bloque le clic : `[aria-disabled]` garde donc
   `pointer-events: none`. Les deux règles disent la même intention sur deux
   supports qui ne se défendent pas pareil. */
.btn:disabled {
  opacity: .55; cursor: not-allowed;
  pointer-events: auto;
}
.btn[aria-disabled="true"] {
  opacity: .55; cursor: not-allowed;
  pointer-events: none;
}
.btn:active { transform: translateY(0.5px); }
.btn-plat:disabled:hover { border-color: var(--color-border); }
.btn-primaire:disabled:hover, .btn-primary:disabled:hover { background: var(--color-primary); }
.btn-plat {
  background: var(--color-surface); color: var(--color-foreground);
  border-color: var(--color-border);
}
.btn-plat:hover { border-color: var(--color-hover-border); }

.btn-ghost {
  background: transparent; color: var(--color-foreground);
  border-color: var(--color-border);
}
.btn-ghost:hover { background: var(--color-muted); }
.btn-danger { background: var(--color-destructive); color: var(--color-on-primary); }
.btn-danger:hover { background: #B91C1C; }
.btn-danger-ghost {
  background: transparent; color: var(--color-destructive);
  border-color: var(--color-border);
}
.btn-danger-ghost:hover { background: var(--color-danger-bg); }
.btn-icon {
  min-width: var(--control-h); min-height: var(--control-h);
  padding: var(--space-15); background: transparent;
  color: var(--color-foreground); border-color: var(--color-border);
}
.btn-icon:hover { background: var(--color-muted); }

/* Le bouton d'icône DISCRET : 30 px, sans bordure ni fond, pour ce qui vit
   DANS une ligne de tableau (supprimer, éditer). À ne pas confondre avec
   `.btn-icon`, qui fait la hauteur d'un contrôle et porte une bordure :
   celui-là se pose dans une barre d'actions. Relevé de rouzes-admin, où il
   était seul, le 04/09/2026. */
.btn-ico {
  display: grid; place-items: center; flex: none;
  width: var(--control-h-icone); height: var(--control-h-icone); min-height: var(--control-h-icone);
  border-radius: var(--radius-full);  /* pilule (décision Cyril, 02/09/2026) */
  border: 1px solid transparent; background: none; cursor: pointer;
  color: var(--color-muted-fg);
  transition: background var(--transition-fast), color var(--transition-fast);
}
.btn-ico:hover { background: var(--color-muted); color: var(--color-foreground); }
.btn-ico.danger:hover {
  color: var(--color-destructive);
  background: color-mix(in srgb, var(--color-destructive) 8%, transparent);
  border-color: color-mix(in srgb, var(--color-destructive) 32%, transparent);
}
.btn-ico .ico { width: 16px; height: 16px; }
/* Au doigt, 30 px ne se vise pas : le seuil de l'ERP est 44. */
@media (max-width: 640px) {
  .btn-ico { width: 44px; height: 44px; min-height: 44px; }
}


/* ── UN SEUL VOCABULAIRE DE BOUTONS, EN DEUX LANGUES (04/09/2026) ─────────
   L'ERP a grandi en deux familles. Les six modules statiques nomment leurs
   boutons en français — .btn-primaire, .btn-plat, .btn-compact, .danger,
   .btn-ico — et les deux applications Next en anglais — .btn-primary,
   .btn-ghost, .btn-danger, .btn-danger-ghost, .btn-icon.

   Relevé le 04/09/2026 : 274 appels d'un côté, 130 de l'autre, et AUCUN ne
   traverse la frontière. Chaque socle ne portait donc que la moitié du
   vocabulaire. Un module déplacé d'une famille à l'autre, ou une règle
   remontée d'un module vers le socle, perdait ses boutons — une classe qui
   n'existe pas ne lève rien, elle ne peint simplement pas.

   Les DEUX socles portent désormais le vocabulaire entier, aux mêmes
   valeurs. Rien n'a été renommé dans le balisage : les 404 appels existants
   marchent à l'identique, et un renommage éventuel devient un remplacement
   de texte sans aucun travail CSS.

   DEUX PAIRES NE SONT PAS DES SYNONYMES, et c'est délibéré :

     .btn-plat a un fond BLANC, .btn-ghost est TRANSPARENT. Sur le sable du
     thème Atelier, les deux ne donnent pas la même chose — et c'est
     justement là que les modules statiques vivent.

     .btn-ico fait 30 px et n'a ni bordure ni fond ; .btn-icon fait la
     hauteur d'un contrôle et porte une bordure. L'un se pose DANS une ligne
     de tableau, l'autre dans une barre d'actions. Les fondre changerait
     l'un des deux.

   Ces deux arbitrages restent ouverts : les trancher est un choix visuel,
   pas un renommage.
   ────────────────────────────────────────────────────────────────────── */

.btn-primaire, .btn-primary {
  background: var(--color-primary); color: var(--color-on-primary);
  border-color: var(--color-primary);
}
.btn-primaire:hover, .btn-primary:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }

/* ── Deux tailles de pilule dans tout l'ERP (décision Cyril, 02/09/2026) ──
   L'ACTION garde `--control-h` (36 px, 44 au tactile) : Rafraîchir,
   Enregistrer, les dialogues. Le COMPACT fait 28 px pour ce qui vit dans une
   ligne de tableau ou une barre de filtres : les chips ET les boutons de
   ligne (« Suivi », « Annuler ») partagent cette hauteur — un bouton de 36 px
   dans une ligne dominait la ligne, et il ne s'alignait sur rien. */
/* `min-height` AUTANT que `height` : `.btn` pose un plancher a
   --control-h (36 px), et un plancher ecrase une hauteur, quelle que soit la
   specificite du selecteur. Sans cette ligne le compact fait 36 px et ne
   s'aligne plus sur les chips, qui font 28. */
.btn-compact { height: var(--control-h-compact); min-height: var(--control-h-compact); padding: 0 var(--space-3); font-size: var(--fs-xs); }
.chips { display: flex; gap: var(--space-15); flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; height: var(--control-h-compact); padding: 0 var(--space-3);
  border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-full);
  font: inherit; font-size: var(--fs-xs); cursor: pointer; color: inherit;
}
.chip.actif { background: var(--color-primary); color: var(--color-on-primary); border-color: var(--color-primary); }
/* Un bouton compact posé dans le flux d'un texte (résumé de fiche, ligne de
   liste, note) ou à côté d'un autre dans une cellule : un souffle à gauche.
   Les cellules qui alignent leurs boutons en flex (`.actions-ligne`) ont
   déjà leur `gap` : la règle vise les enfants DIRECTS du td. */
summary .btn-compact, li .btn-compact, p .btn-compact, td > .btn-compact + .btn-compact {
  margin-left: var(--space-2); vertical-align: middle;
}
/* Un bouton SÉLECTIONNÉ (filtre pressé, fiche dépliée, bascule de vue) se peint
   comme la chip active : fond primaire, texte blanc — même logique partout
   (décision Cyril, 02/09/2026). Le survol ne le délave pas. */
.btn[aria-pressed="true"], .btn[aria-expanded="true"], .btn.actif,
.btn[aria-pressed="true"]:hover, .btn[aria-expanded="true"]:hover, .btn.actif:hover {
  background: var(--color-primary); color: var(--color-on-primary); border-color: var(--color-primary);
}

/* ── UNE COULEUR = UN RÔLE (décision Cyril, 22/08/2026) ──────────────────
   Le plein de couleur est réservé à l'ACTION, et l'action a une seule
   couleur dans tout l'ERP (`--color-primary`). Avant cette règle, trois
   systèmes se disputaient le même rôle visuel : les treize teintes de module
   du hub, l'azur propre du hub, et la couleur du thème. Une même couleur
   disait tantôt « qui je suis », tantôt « clique ici ».

   Conséquences tenues ici, une fois pour toutes :

   - L'IDENTITÉ d'un module ne peint plus qu'une icône : `--color-teinte`.
     Elle était recopiée en dur dans les cinq feuilles de module — et
     `rouzes-commandes` en portait DEUX valeurs différentes (#B45309 dans
     commandes.css, #475569 dans atelier.css, qui n'est pas chargé : la
     divergence dormait). La source de vérité est MODULES[] du hub.
   - Les ÉTATS (vert / ambre / rouge) ne peignent que des pastilles et des
     badges. Jamais un bouton.
   - Le DANGER se dit par la couleur du TEXTE et un filet pâle, jamais par un
     fond plein : `rouzes-commandes` posait `background: var(--color-destructive)`
     là où chantiers et biblio n'en coloraient que le texte. Un bouton rouge
     plein criait plus fort que le bouton d'action lui-même.

   Un seul rouge dans l'application : `--color-destructive`. `.lien.danger`
   le prenait en dur (#B42318), donc un thème ne pouvait pas le suivre. */
.ico-marque {
  width: 22px; height: 22px;
  color: var(--color-teinte, var(--color-muted-fg));
}
.btn.danger, .btn-plat.danger {
  color: var(--color-destructive);
  border-color: color-mix(in srgb, var(--color-destructive) 32%, transparent);
}
.btn.danger:hover, .btn-plat.danger:hover {
  background: color-mix(in srgb, var(--color-destructive) 8%, transparent);
  border-color: color-mix(in srgb, var(--color-destructive) 55%, transparent);
}
/* ── Boutons et liens ────────────────────────────────────────────────── */
.lien {
  border: 0; background: none; padding: 0;
  color: var(--color-primary); font: inherit; cursor: pointer;
  text-decoration: underline;
}
/* `.lien.danger` (Tour de correction 1, Tâche 14a) : posé depuis longtemps
   sur les boutons « annuler »/« refuser »/« supprimer » (commandes.js,
   chantiers.js…) mais jamais stylé — seul `.btn.danger` l'était (une
   feuille de module, pas le socle). Un bouton de suppression DÉFINITIVE
   affiché identique à « imprimer » ou « annuler » n'attire pas plus
   l'attention qu'un lien ordinaire. Même rouge que `.btn.danger`
   (chantiers.css) : un seul rouge « danger » dans toute l'application. */
.lien.danger { color: var(--color-destructive); }
.lien.danger:hover { color: color-mix(in srgb, var(--color-destructive) 75%, #000); }

/* `padding: 0` donne à `.lien` la hauteur de son texte, soit ~15 à 18 px :
   sous le doigt, tous les « supprimer », « annuler », « prix » et « retirer »
   de l'ERP étaient deux fois trop petits.

   ⚠ PREMIÈRE VERSION RETIRÉE, ET POURQUOI. J'avais étendu la zone par un
   pseudo-élément de 44 px centré, pour ne décaler aucune ligne. Mesuré sur
   la Bibliothèque connectée — 273 boutons `.lien` empilés tous les 31 px —
   le résultat était PIRE que le défaut : les zones se RECOUVRAIENT. La
   hauteur réellement cliquable retombait à 31 px, et à 15 px sous le centre
   d'un bouton, c'est le bouton de la LIGNE SUIVANTE qui répondait. Viser
   « prix » sur une référence et modifier celle d'en dessous, dans un
   catalogue de tarifs, est un défaut plus grave que la cible trop petite.

   Une zone tactile qui déborde de son élément vole celle de son voisin dès
   que l'espacement des lignes est inférieur à sa hauteur. Le `padding`, lui,
   pousse le voisin au lieu de le recouvrir : la liste devient plus haute —
   c'est le prix honnête d'une liste utilisable au doigt — et deux cibles ne
   se chevauchent jamais.

   Mesuré sur la Bibliothèque connectée, à 375 px : boîte 15 px → 45 px,
   hauteur réellement cliquable 31 px → 45 px, et ce qui répond juste sous un
   bouton n'est plus le bouton voisin mais la cellule du tableau. */
@media (max-width: 640px) {
  .lien { padding-block: 15px; }
}

/* ── Onglets ─────────────────────────────────────────────────────────── */
.onglets {
  display: flex; gap: var(--space-1); flex-wrap: wrap;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-5);
}
.onglet {
  display: inline-flex; align-items: center; gap: var(--space-2);
  border: 0; background: none; cursor: pointer;
  padding: var(--space-3) var(--space-4);
  font: 500 var(--fs-control) var(--font-body);
  color: var(--color-muted-fg);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}
.onglet:hover { color: var(--color-foreground); }
.onglet .ico { width: 17px; height: 17px; }

/* ── Tableaux ────────────────────────────────────────────────────────── */
/* ── Tableaux ────────────────────────────────────────────────────────── */
.defile { overflow-x: auto; }
.tableau { border-collapse: collapse; width: 100%; font-size: var(--fs-sm); }
.tableau th, .tableau td {
  text-align: left; padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--color-border);
  white-space: nowrap;
}
.tableau th {
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .04em;
  color: var(--color-muted-fg); background: var(--color-muted);
}
.tableau td.num { text-align: right; font-family: var(--font-mono); font-size: var(--fs-sm); }
/* L'en-tête d'une colonne chiffrée s'aligne sur ses valeurs (04/10/2026). */
.tableau th.num { text-align: right; }

/* Bandeau de ZONE d'un tableau à deux niveaux (zone, puis sections) —
   fiche de débit publiée du module Chantiers, vue « par zone » du module
   Débit. Il vit ICI et non dans chaque feuille de module : né le
   30/08/2026 en deux exemplaires, il DIVERGEAIT déjà le jour même (l'un
   portait `font-size`, l'autre non).

   `tr.section.section-zone` et non `tr.section-zone` : les modules
   définissent chacun leur `.tableau tr.section` — même spécificité, et
   leur feuille se charge APRÈS celle du socle, donc leur fond gagnerait.
   Le sélecteur à deux classes tranche sans dépendre de l'ordre. */
.tableau tr.section.section-zone td {
  background: color-mix(in srgb, var(--color-primary) 14%, var(--color-muted));
  color: var(--color-foreground);
  font-size: var(--fs-xs);
  letter-spacing: .06em;
}

/* Bascule Global | Par zone d'un débit (19/08/2026, dossier chantier ;
   étendue au module Commandes le 30/08/2026). Montée ICI pour la MÊME
   raison que le bandeau ci-dessus : elle vivait dans chantiers.css seul, et
   le deuxième écran qui en avait besoin l'aurait recopiée — deux boutons
   d'apparence identique divergeant au premier ajustement. */
.bascule-zone { display: inline-flex; gap: var(--space-05); margin: var(--space-2) 0;
  border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: var(--space-05); }
.bascule-zone .vz {
  border: 0; background: none; cursor: pointer;
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-full);
  /* EN LONGHANDS : `font: 600 var(--fs-xs) inherit` est invalide au calcul (un
     mot-clé global ne peut pas être un composant d'un raccourci) et les
     boutons retombaient sur le body, 15 px / 400 (CSA-10). */
  font-weight: 600; font-size: var(--fs-xs); font-family: inherit;
  color: var(--color-muted-fg);
}
.bascule-zone .vz.est-actif { background: var(--color-primary); color: #fff; }
/* Palier tactile (remonté de debit.css) : ~20 px de haut au doigt, c'est sous
   le seuil de 2.5.8. Par la LARGEUR, pas `pointer: coarse` (Surface Pro). */
@media (max-width: 900px) { .bascule-zone .vz { padding: var(--space-3) var(--space-4); } }

/* ── Connexion ───────────────────────────────────────────────────────── */
.connexion { display: grid; place-items: center; padding: 48px 0; }
.carte-connexion {
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-lg); padding: var(--space-6);
  max-width: 420px; display: flex; flex-direction: column; gap: var(--space-3);
}
/* h2 depuis le 15/09/2026 : le h1 de la page est la marque de l'en-tête. */
.carte-connexion h2 { margin: 0; font-size: var(--fs-xl); }
.carte-connexion p { margin: 0 0 var(--space-2); color: var(--color-muted-fg); font-size: var(--fs-sm); }
.carte-connexion label { display: flex; flex-direction: column; gap: var(--space-1); }
.carte-connexion label > span {
  font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: .04em; color: var(--color-muted-fg);
}
/* `height` passe par le jeton de contrôle plutôt que par 38 px en dur : c'est
   ce qui fait remonter le champ à 44 px sous 640 px de large (cf. le bloc
   « AU DOIGT » de tokens.css). En dur, l'écran de connexion était le seul du
   socle à échapper à cette remontée — alors que c'est le tout premier que
   rencontre chaque utilisateur, sur les neuf modules.

   La taille reste à `--fs-body` (15 px) et NON à `--fs-control` (13,5 px) :
   passer par le jeton des contrôles aurait rapetissé le champ sur bureau. */
.carte-connexion input {
  height: var(--control-h); padding: 0 var(--space-3);
  border: 1px solid var(--color-border); border-radius: var(--radius-md);
  background: var(--color-surface); color: var(--color-foreground);
  font: 400 var(--fs-body) var(--font-body);
}
/* 16 px EXACTEMENT, pas 15 : sous 16 px, Safari iOS agrandit la page au focus
   d'un champ et ne la réduit jamais ensuite. Un pixel sépare le champ correct
   du champ qui fait sauter la mise en page à chaque saisie. */
@media (max-width: 640px) {
  .carte-connexion input { font-size: var(--fs-md); }
}

/* Les toasts « satin » vivent dans css/toasts.css : le hub les charge
   sans le reset de ce fichier. */

/* ── Barre de filtres : la recherche et le sélecteur ─────────────────────
   Montés ici le 03/09/2026. Avant, chaque module se débrouillait : l'écran
   Affaires donnait à sa recherche la classe `.saisie`, qui est le MINI-CHAMP
   NUMÉRIQUE des tableaux de chantiers.css (76 px de large, 26 de haut, mono,
   aligné à droite), et à son sélecteur la classe `.filtre-select`, qui
   n'existait que dans rouzes-admin — donc nulle part chez lui. Une classe
   absente ne lève rien : seul l'œil la voit.

   Rayon 999 px, comme les boutons et les chips depuis le 02/09/2026 : la
   règle de rouzes-admin, la seule qui existait, était encore en
   `--radius-md`. Le déplacement l'aligne sur le reste de l'ERP. */
.champ-recherche {
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--control-h); padding: 0 var(--space-3);
  min-width: 240px; flex: 1 1 240px; max-width: 380px;
  background: var(--color-surface); color: var(--color-foreground);
  border: 1px solid var(--color-border); border-radius: var(--radius-full);
}
.champ-recherche:focus-within {
  border-color: var(--color-ring);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-ring) 18%, transparent);
}
.champ-recherche .ico {
  width: 16px; height: 16px; color: var(--color-muted-fg); flex: none;
}
/* L'input n'a NI bordure NI fond : c'est la pilule qui les porte. Sans
   `min-width: 0`, un input dans un flex refuse de descendre sous sa taille
   par défaut (~180 px) et fait déborder la pilule. */
.champ-recherche input {
  border: 0; background: none; outline: none;
  width: 100%; min-width: 0;
  font: 400 var(--fs-control) var(--font-body); color: inherit;
}
/* La croix native de `type="search"` : Chrome la peint en gris foncé sur
   fond clair et elle disparaît en thème sombre. On la laisse, mais on lui
   donne la couleur du texte secondaire. */
.champ-recherche input::-webkit-search-cancel-button {
  cursor: pointer; opacity: .5;
}

/* Le chevron est une `data:` URI : aucune requête, donc rien à autoriser
   dans la CSP, et le sélecteur reste stylable (`appearance: none` retire le
   chevron natif, il faut donc le redessiner). */
.select-filtre {
  height: var(--control-h); padding: 0 var(--space-6) 0 var(--space-4);
  border: 1px solid var(--color-border); border-radius: var(--radius-full);
  background: var(--color-surface) no-repeat right 12px center/10px 6px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath fill='%235E6268' d='M0 0h10L5 6z'/%3E%3C/svg%3E");
  color: var(--color-foreground);
  font: 500 var(--fs-control) var(--font-body);
  cursor: pointer; appearance: none;
}
.select-filtre:hover { border-color: var(--color-hover-border); }
.select-filtre:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 1px; }

/* ── La barre de pilules (29/09/2026) ────────────────────────────────────
   Celle des Statistiques (`.acc-vues` / `.acc-vue`, facturation.css), montée
   ici pour que l'onglet Achats porte LA MÊME : une piste grise arrondie, des
   pilules sans bordure, la sélection en primaire. Deux copies auraient dérivé
   au premier réglage (cf. `.bascule-zone`, 30/08). La sélection se lit sur
   `aria-pressed`, comme pour `.btn`. Sous 640 px la piste défile au lieu de
   passer à la ligne : une piste arrondie sur deux lignes ne ressemble à rien.
   Hauteur TOTALE = --control-h-compact (29/09, Cyril : « toutes les pilules
   doivent avoir la même hauteur ») : la piste s'aligne sur la recherche et
   les boutons compacts posés à côté ; les pilules intérieures en prennent
   le reste. */
.barre-pilules {
  display: flex; align-items: stretch; gap: var(--space-05);
  width: fit-content; max-width: 100%; box-sizing: border-box;
  height: var(--control-h-compact);
  background: var(--color-muted); border: 1px solid var(--color-border);
  border-radius: var(--radius-full); padding: var(--space-05);
}
.barre-pilules > button {
  flex: none; display: inline-flex; align-items: center;
  border: 0; background: none; border-radius: var(--radius-full);
  padding: 0 var(--space-3); font: 500 var(--fs-xs) var(--font-body);
  color: var(--color-muted-fg); cursor: pointer; white-space: nowrap;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.barre-pilules > button[aria-pressed="true"] {
  background: var(--color-primary); color: var(--color-on-primary);
}
.barre-pilules > button:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 1px; }
@media (max-width: 640px) {
  .barre-pilules { overflow-x: auto; scrollbar-width: none; }
}

/* ── L'étiquette d'état ──────────────────────────────────────────────────
   Un FAIT posé sur une carte ou dans une ligne : une date limite, un nombre
   de pièces jointes, une relance due. Trois variantes seulement, et jamais
   deux codes couleur pour deux sens sur le même écran. */
.etiq {
  display: inline-flex; align-items: center; gap: var(--space-1);
  height: 20px; padding: 0 var(--space-2); border-radius: var(--radius-full);
  font: 500 var(--fs-2xs) var(--font-body);
  background: var(--color-muted); color: var(--color-muted-fg);
  border: 1px solid transparent;
}
.etiq .ico { width: 12px; height: 12px; }
/* La MICRO-pilule : la même forme, un cran plus bas. Née des cartes du plan
   de charge (03/09/2026) où trois faits — l'agence, la relance, les pièces —
   se posent côte à côte sous un objet : à 20 px chacune, elles pesaient plus
   que le texte qu'elles commentent. */
.etiq.mini { height: 18px; padding: 0 var(--space-2); font-size: var(--fs-3xs); }
.etiq.mini .ico { width: 11px; height: 11px; }
.etiq.urgent {
  background: color-mix(in srgb, var(--color-destructive) 12%, transparent);
  color: var(--color-danger-fg);
  border-color: color-mix(in srgb, var(--color-destructive) 30%, transparent);
}
.etiq.bientot {
  background: color-mix(in srgb, var(--color-warning) 14%, transparent);
  color: var(--color-warn-fg);
  border-color: color-mix(in srgb, var(--color-warning) 32%, transparent);
}
.etiq.ok {
  background: color-mix(in srgb, var(--color-success) 12%, transparent);
  color: var(--color-ok-fg);
  border-color: color-mix(in srgb, var(--color-success) 30%, transparent);
}

/* ── L'étiquette navette Sud (15/09/2026) ────────────────────────────────
   UN seul objet, rendu par `etiquetteNavette()` (socle/chargements.js) et
   posé à l'identique dans les trois écrans : la fiche de débit qui la
   demande, la file des achats qui la lit, le bon de préparation qui la
   porte. Ce qui distingue le SOUHAIT du chargé d'affaires du DÉPART réel
   n'est pas la couleur mais la FORME : pointillé tant que personne n'a mis
   le bon dans un camion, plein dès qu'un départ le porte — et le code du
   départ (SE38) n'apparaît que là.
   Au socle et non dans trois feuilles : née en trois exemplaires, elle
   aurait divergé au premier ajustement (cf. .bascule-zone, 30/08). */
.etiquette-navette {
  --etiq-encre: var(--color-primary);
  display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--space-2);
  max-width: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border: 1.5px dashed var(--etiq-encre);
  background: color-mix(in srgb, var(--etiq-encre) 3%, transparent);
  color: var(--etiq-encre);
  font-size: var(--fs-sm);
  line-height: 1.25;
  vertical-align: middle;
}
.etiquette-navette .ico { width: 18px; height: 18px; flex: none; color: inherit; }
.etiquette-navette-corps { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* `--font-titres` n'est déclaré que dans les thèmes : dans une feuille du
   socle il serait fantôme au banc css-jetons. */
.etiquette-navette-kicker {
  font-family: "Archivo", var(--font-body); font-weight: 700; font-size: var(--fs-3xs);
  text-transform: uppercase; letter-spacing: .09em; opacity: .85;
}
.etiquette-navette-route {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1);
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 600;
}
.etiquette-navette-route b { font-weight: 700; }
.etiquette-navette-trait {
  flex: 0 1 16px; min-width: 8px; height: 0; margin: 0 var(--space-05);
  border-top: 2px dashed currentColor; opacity: .5; position: relative; align-self: center;
}
.etiquette-navette-trait::after {
  content: ""; position: absolute; right: -1px; top: 50%; translate: 0 -50%;
  border: 4px solid transparent; border-left-color: currentColor; opacity: .85;
}
/* Aucun arrêt choisi : l'atelier tranchera selon l'agence du chantier. En
   italique, parce que ce n'est pas encore une destination. */
.etiquette-navette-dest.est-incertaine { font-style: italic; font-weight: 500; opacity: .85; }
.etiquette-navette-code {
  flex: none; margin-left: var(--space-1); padding: 1px var(--space-15);
  border-radius: var(--radius-full); font-family: var(--font-mono);
  font-size: var(--fs-3xs); font-weight: 700; letter-spacing: .03em;
  background: color-mix(in srgb, currentColor 18%, transparent);
}
.etiquette-navette[data-etat="confirmee"] {
  border-style: solid; background: var(--etiq-encre); color: var(--color-on-primary);
}
/* Le format compact est le MÊME dessin en plus petit — jamais une autre
   forme : c'est ce qui fait qu'on le reconnaît d'un écran à l'autre. */
.etiquette-navette.compacte {
  padding: var(--space-05) var(--space-2); gap: var(--space-1); font-size: var(--fs-2xs);
}
.etiquette-navette.compacte .ico { width: 14px; height: 14px; }
.etiquette-navette.compacte .etiquette-navette-route { font-size: var(--fs-3xs); }
.etiquette-navette-legende { margin-top: var(--space-1); }
/* La réserve : « chantier hors agences Sud ». En italique et d'un ton plus
   bas — c'est une nuance sur la destination, pas une seconde destination. */
.etiquette-navette-note {
  flex: none; margin-left: var(--space-1); font-family: var(--font-body);
  font-size: var(--fs-3xs); font-style: italic; font-weight: 500; opacity: .8;
}

/* ── La relance des achats (15/09/2026) ──────────────────────────────────
   Le compte de relances se lit SANS lire la phrase : trois barres qui
   s'allument, et une teinte qui monte d'ambre à rouge. La couleur d'état ne
   peint jamais le fond d'un bouton (règle de cette feuille) — elle peint le
   cadre de l'alerte, et le bouton se contente d'en prendre l'encre. */
.jauge-intensite { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; flex: none; }
.jauge-intensite span { width: 3px; border-radius: 1px; background: var(--color-border); }
.jauge-intensite span:nth-child(1) { height: 5px; }
.jauge-intensite span:nth-child(2) { height: 8px; }
.jauge-intensite span:nth-child(3) { height: 12px; }
[data-niveau="1"] .jauge-intensite span:nth-child(1) { background: var(--color-warning); }
[data-niveau="2"] .jauge-intensite span:nth-child(-n+2) { background: var(--color-warning); }
[data-niveau="3"] .jauge-intensite span { background: var(--color-destructive); }

.alerte-relance {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3); margin: var(--space-2) 0;
  border-radius: var(--radius-md); border: 1px solid var(--color-warn-border);
  background: var(--color-warn-bg-soft);
}
.alerte-relance .ico { color: var(--color-warning); flex: none; }
.alerte-relance-texte { margin: 0; flex: 1 1 220px; font-size: var(--fs-sm); color: var(--color-foreground); }
.alerte-relance-texte b { color: var(--color-warn-fg); }
.alerte-relance[data-niveau="2"] { background: var(--color-warn-bg); border-color: var(--color-warning); }
.alerte-relance[data-niveau="3"] { background: var(--color-danger-bg); border-color: var(--color-destructive); }
.alerte-relance[data-niveau="3"] .ico { color: var(--color-destructive); }
.alerte-relance[data-niveau="3"] .alerte-relance-texte b { color: var(--color-danger-fg); }
.alerte-relance .btn { margin-left: auto; }

/* Le bouton de relance se désigne par son ATTRIBUT et non par une classe
   `btn-…` : le banc css-jetons exige qu'un nom de bouton du socle existe
   aussi dans rouzes-ui/base.css (partagé avec le Devis et le Planning), et
   un bouton propre à la relance n'a rien à y faire. */
.btn[data-relance] { display: inline-flex; align-items: center; gap: var(--space-15); }
.btn[data-relance][data-niveau="3"] {
  color: var(--color-destructive);
  border-color: color-mix(in srgb, var(--color-destructive) 32%, transparent);
}
.btn[data-relance][data-niveau="3"]:hover {
  background: color-mix(in srgb, var(--color-destructive) 8%, transparent);
  border-color: color-mix(in srgb, var(--color-destructive) 55%, transparent);
}

/* ==========================================================================
   COMPOSANTS COMMUNS remontés le 15/09/2026 (harmonisation). Ils vivaient
   dans rouzes-ui/base.css (Next) et, en copies divergentes, dans les feuilles
   de module. Valeurs = celles de base.css, sauf : .badge reçoit une hauteur
   fixe (--badge-h), .modal lit --shadow-lg et --overlay-fort.
   ========================================================================== */
.input, .select {
  font: inherit; font-size: var(--fs-control); color: var(--color-foreground);
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); min-height: var(--control-h);
  padding: var(--space-15) var(--space-25); outline: none; min-width: 0;
}
.input:hover, .select:hover { border-color: var(--color-hover-border); }
.input:focus-visible, .select:focus-visible { border-color: var(--color-primary); box-shadow: 0 0 0 3px var(--color-ring); }
.input:invalid { box-shadow: none; }
.input[readonly] { background: var(--color-muted); color: var(--color-muted-fg); cursor: default; }
textarea.input { resize: vertical; min-height: calc(var(--control-h) + var(--space-6)); }

/* .champ = un libellé au-dessus d'un contrôle (admin et biblio le portaient à l'identique) */
.champ { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.champ > span, .champ > label { font-size: var(--fs-sm); font-weight: 600; }
.champ .aide { margin: 0; font-size: var(--fs-xs); color: var(--color-muted-fg); }

/* La pilule d'état : hauteur FIXE, texte centré. */
.badge {
  display: inline-flex; align-items: center; height: var(--badge-h);
  padding: 0 var(--space-2); border-radius: var(--radius-full);
  font-size: var(--fs-2xs); font-weight: 600; line-height: 1; white-space: nowrap;
  background: var(--color-muted); color: var(--color-muted-fg);
}
.badge-neutre { background: var(--color-muted); color: var(--color-muted-fg); }
.badge-ok     { background: var(--color-ok-bg); color: var(--color-ok-fg); border: 1px solid var(--color-ok-border); }
.badge-warn   { background: var(--color-warn-bg); color: var(--color-warn-fg); border: 1px solid var(--color-warn-border); }
.badge-danger { background: var(--color-muted); color: var(--color-danger-fg); }
.badge-info   { background: var(--color-info-bg); color: var(--color-info-fg); }

/* LA CROIX DE SUPPRESSION — montée au socle le 17/09/2026 (demande de Cyril :
   « une croix comme sur les cartes chantier, et sur tous les sites »). Elle
   vivait depuis le 24/08 dans rouzes-chantiers seul ; tout module qui a des
   cartes supprimables l'emploie désormais sans la recopier.

   TOUJOURS visible, jamais révélée au survol : `hover` n'existe pas sous le
   doigt, et le pointeur déclaré MENT sur la Surface de Cyril (pointer:coarse
   en permanence) — une croix qui n'apparaîtrait qu'au survol y serait
   inatteignable. Discrète au repos, franchement rouge quand on la vise.

   Le parent doit porter `position: relative`. */
.carte-suppr {
  position: absolute; top: 6px; right: 6px;
  width: 26px; height: 26px; border-radius: var(--radius-full); /* un bouton icône carré devient un rond (décision Cyril, 02/09/2026) */
  display: grid; place-items: center;
  font-size: var(--fs-sm); line-height: 1;
  color: var(--color-muted-fg); opacity: .45;
  background: none; border: 0; padding: 0;
  cursor: pointer; transition: opacity var(--transition-fast),
    color var(--transition-fast), background var(--transition-fast);
}
.carte-suppr:hover, .carte-suppr:focus-visible {
  opacity: 1; color: var(--color-surface); background: var(--color-destructive);
}
.carte-suppr.occupee { opacity: .3; pointer-events: none; }
/* SOC-06 : au doigt, 26 px ne se visent pas — le seuil de l'ERP est 44. Le
   palier est celui de .btn-ico (largeur de téléphone) et de taches.css (un
   téléphone couché a moins de 520 px de haut) ; le pointeur déclaré, lui,
   ment sur la Surface. La croix reste dans le COIN de la carte (top/right 0) :
   la sortir de la carte volerait la zone du voisin. Les modules qui posent
   quelque chose à côté d'elle (le signal de rouzes-chantiers) recalent leur
   décalage sur ces 44 px. */
@media (max-width: 640px), (max-height: 520px) {
  .carte-suppr { top: 0; right: 0; width: 44px; height: 44px; }
}

/* La carte : la surface de base des modules (facturation.css de chantiers et
   de stats la portaient au caractère près). SANS padding : les modules la
   découpent en .carte-tete / .carte-corps qui portent le leur — un padding
   ici le doublerait. */
.carte { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); }
.carte-corps { padding: var(--space-4); }

.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-3); }
.kpi { display: flex; flex-direction: column; gap: var(--space-05); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: var(--space-2) var(--space-4); min-width: 150px; }
.kpi-label { font-size: var(--fs-xs); color: var(--color-muted-fg); font-weight: 500; }
.kpi-value { font-family: var(--font-mono); font-size: var(--fs-xl); font-weight: 600; font-variant-numeric: tabular-nums; }
.kpi-value .unit { font-size: var(--fs-sm); color: var(--color-muted-fg); font-family: var(--font-body); }
.kpi.kpi-ok .kpi-value { color: var(--color-success); }
.kpi.kpi-warn .kpi-value { color: var(--color-warning); }
.kpi.kpi-danger .kpi-value { color: var(--color-destructive); }

/* La modale, sur <dialog class="modal"> */
.modal { border: 0; border-radius: var(--radius-lg); padding: 0; width: min(480px, calc(100vw - var(--space-6))); box-shadow: var(--shadow-lg); z-index: var(--z-modal); }
.modal-lg { width: min(620px, calc(100vw - var(--space-6))); }
.modal-confirm { width: min(410px, calc(100vw - var(--space-6))); }
.modal::backdrop { background: var(--overlay-fort); }
.modal-form { display: flex; flex-direction: column; max-height: min(85dvh, 720px); }
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-border); }
.modal-head h2 { margin: 0; font-size: var(--fs-md); }
.modal-body { padding: var(--space-4); overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-3); }
.modal-foot { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--color-border); }
.foot-spacer { flex: 1; }

/* Les toasts vivent dans toasts.css (.toasts-satin / .toast-satin, produits
   par socle/ui.js) : un composant DIFFÉRENT du .toast de base.css (Next) —
   deux vocabulaires, à fondre au lot E, pas ici. */

/* Gabarit de page (15/09/2026). Les six modules portaient la même règle au
   pied près : centrée, marges du socle, pied de --page-pied. Chaque module ne
   garde que SA largeur (`.page { max-width: … }`). Les sections de main.page
   se suivent à un pas fixe. */
.page { margin: 0 auto; padding: var(--space-6) var(--space-5) var(--page-pied); }
@media (max-width: 640px) {
  .page { padding: var(--space-4) var(--space-3) var(--page-pied-tactile); }
}
main.page > * + * { margin-top: var(--space-4); }

/* ==================== socle-erp/css/toasts.css ==================== */
/* Socle ERP — les toasts (19/08/2026, « satin », piste C du banc d'essais ;
 * habillés en BILLET le 04/10/2026, demande de Cyril).
 *
 * SOURCE UNIQUE recopiée par `node socle-erp/sync.mjs`.
 *
 * Fichier À PART de composants.css : le hub charge celui-ci sans hériter
 * du reset des modules. Chaque var() porte sa valeur de repli — le hub ne
 * charge pas tokens.css, ses toasts doivent se peindre quand même.
 *
 * Le billet : talon teinté du ton (icône, ou initiales) · coupe pointillée ·
 * corps. Les encoches sont de VRAIS trous (masque), pas des disques couleur
 * de page comme dans billet.css : un toast flotte au-dessus de n'importe
 * quoi, un disque peint s'y verrait. */
/* La pile est ouverte en `popover` par ui.js pour passer AU-DESSUS des
   <dialog> (cf. le commentaire de pileSatin). La feuille du navigateur pose
   alors sur tout [popover] : inset: 0, margin: auto, border: solid,
   padding: .25em, overflow: auto, background: Canvas — d'où les annulations
   ci-dessous. Sans elles, la pile s'étirait sur tout l'écran, encadrée. */
.toasts-satin {
  position: fixed; inset: auto 18px 18px auto; z-index: var(--z-toast);
  margin: 0; border: 0; padding: 0; background: none; overflow: visible;
  display: flex; flex-direction: column-reverse; gap: var(--space-2);
  width: min(340px, calc(100vw - 32px));
  pointer-events: none;
  /* L'ombre est portée par la PILE : le masque des encoches rognerait la
     box-shadow d'un billet, alors que le drop-shadow du parent suit la
     découpe, trous compris. */
  filter: drop-shadow(0 6px 14px rgba(15, 23, 42, .16));
}
.toast-satin {
  /* 12,5 px : hors echelle --fs-*, la taille retenue au banc d'essais. Un
     litteral n'est legitime que dans une definition `--x:` — et ces jetons
     vivent ici, pas dans :root, parce que le hub charge cette feuille SEULE. */
  --toast-fs: 12.5px;
  --ts-talon: 40px;
  --ts-ton: var(--color-primary, #008CF8);
  /* Deux trous de 7 px centrés sur la coupe, l'un en haut, l'autre en bas :
     chaque calque couvre une moitié de hauteur (51 %, qui se recouvrent au
     milieu), son trou à son bord. */
  --ts-masque: radial-gradient(circle 7px at var(--ts-talon) 0, #0000 96%, #000) top / 100% 51% no-repeat, radial-gradient(circle 7px at var(--ts-talon) 100%, #0000 96%, #000) bottom / 100% 51% no-repeat;
  position: relative;
  display: grid; grid-template-columns: var(--ts-talon) minmax(0, 1fr);
  min-height: 46px;
  background: var(--color-surface, #fff);
  border: 1px solid var(--color-border, #E2E8F0);
  border-radius: var(--radius-lg, 12px);
  font-weight: 400; font-size: var(--toast-fs); line-height: 1.45;
  font-family: var(--font-body, "Fira Sans", system-ui, sans-serif);
  -webkit-mask: var(--ts-masque);
  mask: var(--ts-masque);
  animation: toast-satin-in .38s cubic-bezier(.34, 1.4, .5, 1);
  pointer-events: auto;
}
.toast-satin.ok { --ts-ton: var(--color-success, #0F8A4E); }
.toast-satin.erreur { --ts-ton: var(--color-destructive, #C2273B); }
.toast-satin.sort { opacity: 0; transform: translateY(8px);
  transition: opacity .22s, transform .22s; }
/* Le talon touche le cadre : il arrondit lui-même ses coins (rayon du cadre
   moins son trait), comme celui du billet. */
.toast-satin .ts-talon {
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ts-ton) 13%, var(--color-surface, #fff));
  color: var(--ts-ton);
  border-right: 2px dashed color-mix(in srgb, var(--ts-ton) 35%, var(--color-border, #E2E8F0));
  border-radius: calc(var(--radius-lg, 12px) - 1px) 0 0 calc(var(--radius-lg, 12px) - 1px);
}
.toast-satin .ts-ico { width: 18px; height: 18px; fill: none; stroke: currentColor;
  stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.toast-satin .ts-pastille {
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: var(--fs-2xs); font-weight: 600; color: #fff;
  background: var(--ts-ton);
}
.toast-satin .ts-corps { min-width: 0; align-self: center;
  padding: var(--space-25) var(--space-3);
  color: var(--color-foreground, #0E1B3A); overflow-wrap: anywhere; }
.toast-satin .ts-corps b { display: block; font-size: var(--fs-sm); }
.toast-satin.erreur .ts-corps { color: var(--color-destructive, #C2273B); }
@keyframes toast-satin-in {
  from { transform: translateY(10px) scale(.97); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@media print { .toasts-satin { display: none; } }

/* ==================== socle-erp/css/bulle.css ==================== */
/* Socle ERP — l'infobulle maison (28/09/2026).
 *
 * SOURCE UNIQUE recopiée par `node socle-erp/sync.mjs`, dans le bundle
 * css/socle/design-system.css. Le JS vit dans js/bulle.js : il crée la bulle,
 * y écrit le texte et la place (left/top, --fleche-x, .dessus).
 *
 * Encre et fond INVERSÉS, comme la bulle des graphes de Stats
 * (.bulle-graphe) : se détacher de n'importe quelle carte, et que les deux
 * bulles de l'ERP se ressemblent.
 */
.bulle-socle {
  /* Le [popover] du navigateur se centre à l'écran (inset 0, margin auto),
     rogne son contenu (overflow auto) et porte bordure, fond et marge : tout
     est repris ici — la pointe dépasse, elle ne doit pas être rognée. */
  position: fixed; inset: auto; margin: 0; overflow: visible;
  z-index: var(--z-sommet);
  max-width: min(320px, calc(100vw - 16px));
  padding: var(--space-15) var(--space-25);
  border: 0; border-radius: var(--radius-md);
  background: var(--color-foreground); color: var(--color-background);
  font: 500 var(--fs-xs) / 1.45 var(--font-body);
  white-space: pre-line; overflow-wrap: anywhere;
  box-shadow: var(--shadow-menu);
  pointer-events: none;
}
/* La pointe : un carré tourné, de la couleur de la bulle, à moitié caché
   dessous. `--fleche-x` vise le centre de la cible. */
.bulle-socle::before {
  content: ""; position: absolute; top: -4px;
  left: calc(var(--fleche-x, 50%) - 5px);
  width: 10px; height: 10px; background: inherit;
  transform: rotate(45deg); border-radius: var(--radius-xs) 0 0 0;
}
.bulle-socle.dessus::before {
  top: auto; bottom: -4px; border-radius: 0 0 var(--radius-xs) 0;
}
/* Plusieurs lignes : la première fait titre, la suite se lit atténuée. */
.bulle-socle strong { font-weight: 600; }
.bulle-socle .bulle-suite {
  font-weight: 400;
  color: color-mix(in srgb, var(--color-background) 75%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  .bulle-socle { transition: opacity var(--transition-fast); }
  @starting-style { .bulle-socle { opacity: 0; } }
}
@media print {
  .bulle-socle { display: none; }
}

/* ==================== socle-erp/css/pastille.css ==================== */
/* Socle ERP — la pastille « qui a fait quoi ».
 *
 * SOURCE UNIQUE recopiée par `node socle-erp/sync.mjs`.
 *
 * Sortie des modules le 21/08/2026. `pastille.js` PRODUISAIT le rond depuis
 * le socle, mais son habillage était recopié à la main dans chantiers.css,
 * commandes.css et debit.css — et le hub, qui produit la pastille lui aussi
 * (écran Répartition), ne l'avait jamais recopié : ses initiales
 * s'affichaient en texte nu, sans rond ni couleur, posées de travers sur le
 * nom. Rien ne le signalait : une classe absente ne lève pas d'erreur.
 *
 * Le déplacement est à rendu IDENTIQUE — les trois copies étaient les mêmes
 * au caractère près, ce qui était la condition posée par la note de
 * composants.css pour les réunir.
 *
 * Feuille séparée plutôt qu'un ajout à composants.css : le hub ne charge
 * PAS composants.css (il porte sa propre palette), et c'est précisément le
 * module qui en avait besoin. Le CSS d'un composant du socle doit pouvoir
 * voyager avec son JS, sans embarquer 160 lignes de décor.
 */

.pastille {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; flex: none;
  background: var(--pastille-teinte, var(--color-primary));
  color: #fff; font-weight: 600; letter-spacing: .01em;
  text-shadow: 0 1px 1px rgba(0, 0, 0, .3);
}
/* Le créateur de l'app (06/10/2026) : de l'or, texte brun foncé — le blanc
   tombait sous 2:1 sur le jaune. #3A2A00 sur #D4A72C (milieu du dégradé) :
   6,2:1. Deux classes pour passer devant le `background` de `.pastille`. */
.pastille.pastille-or {
  background: linear-gradient(140deg, #F7E08A 0%, #D4A72C 50%, #A9801A 100%);
  color: #3A2A00; text-shadow: 0 1px 0 rgba(255, 255, 255, .35);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .4), 0 0 0 1px rgba(169, 128, 26, .55);
}
.pastille-avec-nom { display: inline-flex; align-items: center; gap: var(--space-15); }
.attribution {
  display: inline-flex; align-items: center; gap: var(--space-15);
  font-size: var(--fs-xs); font-weight: 400; color: var(--color-muted-fg);
  margin-left: var(--space-2);
}
/* Personne ne porte la ligne (30/09/2026, spec billet-affaire §1.1) : un
   anneau en pointillé et un « ? », jamais une couleur — la teinte d'une
   chaîne vide est stable, elle donnerait une couleur de personne à personne.
   Le « ? » vient d'ici et non du gabarit : `pastilleDe` rend un rond vide,
   ses bancs le vérifient. */
.pastille-vide {
  background: var(--color-surface); color: var(--color-muted-fg);
  border: 1.5px dashed var(--color-muted-fg); box-shadow: none; text-shadow: none;
}
.pastille-vide:empty::after { content: "?"; }

/* ==================== socle-erp/css/billet.css ==================== */
/* Socle ERP — le billet : une ligne de liste, talon · corps | coupe | cases · ⋯.
 *
 * SOURCE UNIQUE recopiée par `node socle-erp/sync.mjs` (bundle
 * design-system.css, juste après pastille.css).
 *
 * Sorti de rouzes-commandes/css/commandes.css le 30/09/2026 (spec
 * rouzes-chantiers 2026-09-30-billet-affaire, §1.1), à rendu IDENTIQUE, sur
 * le modèle de pastille.css : les noms de classes ne changent pas.
 * `.depot-carre` garde notamment le sien — les écouteurs de dépôt de
 * Commandes s'y accrochent.
 *
 * La règle de l'application : à GAUCHE les infos (talon, corps — cliquer le
 * corps ouvre la fiche), à DROITE les cases, seuls endroits où l'on agit
 * sur la ligne ; le « ⋯ » regroupe le reste.
 *
 * Un billet à cases porte `.a-cases` à côté de son gabarit : `g-c a-cases`
 * (la commande), `g-a a-cases` (l'affaire), et tout gabarit à venir (spec
 * rouzes-chantiers 2026-10-01-billet-demande). `.billet.a-cases` garde la
 * spécificité (0,2,0) de l'ancien `.billet:is(.g-c, .g-a)` : même rendu. Ce
 * qui est propre à un module (le tarif `g-t`) reste dans sa feuille, qui
 * charge après le bundle et garde le dernier mot. La DEMANDE (`g-dm`) est
 * ici depuis le 03/10/2026 : un objet, un billet (spec socle-erp
 * 2026-10-03-billets-d-objet) — même habit dans la fiche et dans le fil.
 *
 * Le seuil du téléphone se mesure sur la LISTE, pas sur l'écran :
 * `@container billets`. Dans Affaires, le volet de 430 px réduit la liste à
 * ~640 px sur un écran de 1100 à 1200 px, alors qu'un billet à quatre cases
 * en demande au moins 740. Le parent de chaque liste de billets porte donc
 * `container: billets / inline-size` (feuille du module).
 */

/* Mesures PARTAGÉES : la coupe tombe au même endroit d'un billet à l'autre.
   `--b-cases` : les colonnes des cases, que le module pose sur sa liste. */
:root { --b-talon: 52px; --b-zone: 112px; --b-menu: 40px; --b-haut: 56px;
  --b-cases: repeat(4, var(--b-zone)); }

.billet { position: relative; margin: var(--space-2) 0; background: var(--color-surface);
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  transition: box-shadow var(--transition-fast); }
.billet .talon { display: grid; place-items: center; }
.billet .talon .ico { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* La teinte du talon (`data-ton`). `g-c` et `g-d`, les gabarits de
   Commandes, en sont des alias : calme et info. */
.billet.g-c .talon, .billet[data-ton="calme"] .talon { background: var(--color-muted); color: var(--color-muted-fg); }
.billet.g-d .talon, .billet[data-ton="info"] .talon { background: color-mix(in srgb, var(--color-info-bg) 70%, var(--color-surface)); color: var(--color-info-fg); }
.billet[data-ton="ok"] .talon { background: var(--color-ok-bg); color: var(--color-ok-fg); }
.billet[data-ton="danger"] .talon { background: var(--color-danger-bg); color: var(--color-danger-fg); }
/* La coupe : un pointillé sur le bord droit du corps, deux encoches (disques
   couleur de page). Le billet ne rogne RIEN (01/10/2026, Cyril : « le trait
   coupe le rond en deux ») : rogné par `overflow: hidden`, le disque
   s'arrêtait au bord intérieur du cadre, et le bord du billet le traversait
   en son diamètre. Chaque disque garde sa moitié intérieure PLUS l'épaisseur
   du bord (clip-path à 50 % − 1 px) : couleur de page, il recouvre le bord,
   qui s'interrompt sur l'encoche. Le talon et le menu, qui touchent le cadre,
   arrondissent eux-mêmes leurs coins. */
.billet .corps { position: relative; display: flex; flex-direction: column; justify-content: center;
  gap: var(--space-05); min-width: 0; padding: 0 var(--space-4); border-right: 2px dashed var(--color-border); }
.billet .corps::before, .billet .corps::after { content: ""; position: absolute; right: -9px; z-index: 1;
  width: 16px; height: 16px; border-radius: 50%; background: var(--color-background); border: 1px solid var(--color-border); }
.billet .corps::before { top: -9px; clip-path: inset(calc(50% - 1px) 0 0 0); }
.billet .corps::after { bottom: -9px; clip-path: inset(0 0 calc(50% - 1px) 0); }
.billet .talon { border-radius: var(--radius-lg) 0 0 var(--radius-lg); }
.billet .billet-menu { border-radius: 0 var(--radius-lg) var(--radius-lg) 0; }
.billet .ch-nom { font-weight: 600; color: var(--color-foreground); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.billet .detail { display: flex; align-items: center; gap: var(--space-1); min-width: 0;
  color: var(--color-muted-fg); font-size: var(--fs-sm); white-space: nowrap; overflow: hidden; }
.billet.est-solde { opacity: .55; }
.billet.est-solde:hover, .billet.est-solde:focus-within { opacity: .9; }
/* Déplié, le soldé reprend son blanc : il ne fait qu'un avec son dépli,
   qui n'est pas estompé (demande Cyril, 05/10/2026). */
.billet.est-solde:has(+ :is(.dm-depli, .dm-outil):not([hidden])) { opacity: 1; }
.billet.survole { outline: 2px dashed var(--color-primary); outline-offset: 2px; }
.billet.billet-halo { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 45%, transparent); }

/* Le billet à cases : talon · corps · cases · menu. */
.billet.a-cases { display: grid; min-height: var(--b-haut);
  grid-template-columns: var(--b-talon) minmax(0, 1fr) var(--b-cases) var(--b-menu); }
.billet.a-cases .zones { display: contents; }
/* Qui porte la ligne : sa pastille pend sous le talon, à cheval sur le bord
   du billet (Cyril, 29/09). La marge du bas fait la place de la pastille
   (12 px sous le bord, anneau compris). z-index 1, comme les encoches : à 2,
   elle passait PAR-DESSUS l'en-tête collant du jour de Commandes
   (.carnet-tete, 2 aussi, mais plus haut dans le DOM) — vu au banc. */
.billet.a-cases { margin-bottom: var(--space-5); }
.billet.a-cases .talon { position: relative; }
.billet.a-cases .talon > .pastille { position: absolute; left: 50%; bottom: -11px; transform: translateX(-50%);
  z-index: 1; box-shadow: 0 0 0 2px var(--color-background); }
.billet-menu { border: 0; border-left: 1px solid var(--color-border); background: none;
  color: var(--color-muted-fg); font-size: var(--fs-lg); cursor: pointer; }
button.billet-menu:hover, .billet-menu[aria-expanded="true"] { color: var(--color-foreground); background: var(--color-muted); }
/* Le menu « ⋯ » : un popover posé par le JS sous son bouton. `display` ne se
   donne QU'OUVERT — sur [popover], il annulerait le masquage du navigateur. */
.billet-actions { position: fixed; inset: auto; margin: 0; padding: var(--space-1); min-width: 200px;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-menu); }
.billet-actions:popover-open { display: grid; }
.billet-actions button { border: 0; background: none; text-align: left; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); font: inherit; font-size: var(--fs-sm); color: var(--color-foreground); cursor: pointer; }
.billet-actions button:hover, .billet-actions button:focus-visible { background: var(--color-muted); }
.billet-actions .danger { color: var(--color-danger-fg); }

/* ── La case (spec rouzes-commandes 2026-09-29-billet-commande) ─────────
   L'ancien carré de 72 px, dans son habit compact : trois lignes — genre,
   valeur, sous-ligne — dans une cellule du billet. */
.depot-carre {
  position: relative; display: flex; flex-direction: column; justify-content: center;
  gap: var(--space-05); min-width: 0; padding: 0 var(--space-3);
  border: 0; border-left: 1px solid var(--color-border); border-radius: 0;
  background: none; color: var(--color-foreground); font: inherit; text-align: left;
}
.zones > .depot-carre:first-child { border-left: 0; }
button.depot-carre { cursor: pointer; }
button.depot-carre:hover { background: color-mix(in srgb, var(--color-muted) 55%, var(--color-surface)); }
button.depot-carre:focus-visible { outline: 2px solid var(--color-ring); outline-offset: -2px; }
.depot-carre .genre { display: flex; justify-content: space-between; align-items: center;
  font-size: var(--fs-3xs); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--color-muted-fg); }
.depot-carre .val { font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.depot-carre .sous { display: flex; align-items: center; gap: var(--space-1);
  font-size: var(--fs-3xs); color: var(--color-muted-fg); white-space: nowrap; overflow: hidden; }
/* Vide ET déposable : une vraie zone de dépôt — cadre pointillé, icône,
   « Déposer » en couleur d'action (demande de Cyril, 29/09). */
.depot-carre[data-deposable] { cursor: copy; background: color-mix(in srgb, var(--color-primary) 5%, var(--color-surface)); }
.depot-carre[data-deposable]::before { content: ""; position: absolute; inset: var(--space-1); pointer-events: none;
  border: 1.5px dashed color-mix(in srgb, var(--color-primary) 55%, transparent); border-radius: var(--radius-md); }
.depot-carre[data-deposable] .genre { color: var(--color-primary); }
.depot-carre .invite { display: flex; align-items: center; gap: var(--space-1);
  color: var(--color-primary); font-size: var(--fs-xs); font-weight: 600; white-space: nowrap; }
.depot-carre .ico-depot { width: 14px; height: 14px; flex: none; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.depot-carre[data-deposable] .sous { color: color-mix(in srgb, var(--color-primary) 70%, var(--color-muted-fg)); }
.depot-carre .sous.retard { color: var(--color-warn-fg); font-weight: 600; }
button.depot-carre[data-deposable]:hover { background: color-mix(in srgb, var(--color-primary) 11%, var(--color-surface)); }
button.depot-carre[data-deposable]:hover::before { border-color: var(--color-primary); }
.depot-carre.depot-actif { background: color-mix(in srgb, var(--color-primary) 14%, var(--color-surface));
  box-shadow: inset 0 0 0 2px var(--color-primary); }
/* Une case d'ACTION (le geste attendu) : cadre PLEIN — on clique, on ne
   dépose pas. Le pointillé reste réservé au dépôt. */
.depot-carre[data-geste] { background: color-mix(in srgb, var(--color-primary) 7%, var(--color-surface)); }
.depot-carre[data-geste]::before { content: ""; position: absolute; inset: var(--space-1); pointer-events: none;
  border: 1.5px solid color-mix(in srgb, var(--color-primary) 45%, transparent); border-radius: var(--radius-md); }
.depot-carre[data-geste] .genre { color: var(--color-primary); }
button.depot-carre[data-geste]:hover { background: color-mix(in srgb, var(--color-primary) 13%, var(--color-surface)); }
button.depot-carre[data-geste]:hover::before { border-color: var(--color-primary); }
/* Les états. */
.depot-carre[data-etat="lu"] { background: color-mix(in srgb, var(--color-info-bg) 60%, var(--color-surface)); }
.depot-carre[data-etat="lu"] .genre, .depot-carre[data-etat="lu"] .val { color: var(--color-info-fg); }
/* La pastille de jonction : à cheval sur la frontière gauche de la case,
   celle qui la relie à l'étape précédente. z-index 1 : à 2, elle passerait
   sur l'en-tête collant du jour (.carnet-tete), comme la pastille du talon. */
.jonction { position: absolute; left: 0; top: 50%; translate: -50% -50%; z-index: 1;
  display: grid; place-items: center; min-width: 18px; height: 18px; padding: 0 var(--space-05);
  border: 1px solid var(--color-border); border-radius: var(--radius-full);
  background: var(--color-surface); color: var(--color-muted-fg);
  font-family: var(--font-mono); font-size: var(--fs-3xs); font-weight: 700; line-height: 1; cursor: help; }
.jonction[data-etat="concorde"] { background: var(--color-ok-bg); border-color: var(--color-ok-fg); color: var(--color-ok-fg); }
.jonction[data-etat="ecart"] { background: var(--color-warn-bg-soft); border-color: var(--color-warn-fg); color: var(--color-warn-fg); }
.jonction[data-etat="nonVerifie"] { background: var(--color-muted); }
.jonction[data-retard] { border: 2px solid var(--color-warn-fg); color: var(--color-warn-fg); }
/* L'échéance de la case active est dépassée : un « ! » rouge. */
.jonction[data-etat="retard"] { border: 2px solid var(--color-danger-fg); color: var(--color-danger-fg); }
.depot-carre[data-etat="na"] { background: repeating-linear-gradient(135deg, transparent 0 6px,
  color-mix(in srgb, var(--color-muted) 70%, transparent) 6px 7px); }
/* `attente` : une étape pas encore atteinte — valeur en gris, police du corps. */
.depot-carre[data-etat="na"] .val, .depot-carre[data-fige] .val, .depot-carre[data-bientot] .val,
.depot-carre[data-etat="attente"] .val {
  color: var(--color-muted-fg); font-family: var(--font-body); font-weight: 400; }
.depot-carre[data-bientot] .val { font-style: italic; }
.depot-carre[data-fige] { cursor: not-allowed; }
.depot-carre[data-doc] { cursor: pointer; }
/* Le compteur est en HAUT À DROITE, sous la marque : en bas à droite il
   recouvrait la fin de « conf. 30/09 » (mesuré : 8 px de chevauchement). */
.doc-nb {
  position: absolute; top: var(--space-1); right: var(--space-2);
  min-width: 14px; height: 14px; padding: 0 var(--space-05);
  border-radius: var(--radius-full); background: var(--color-muted);
  color: var(--color-muted-fg); font-size: var(--fs-3xs); line-height: 14px; text-align: center;
}

/* ── Téléphone : la LISTE a moins de 700 px ─────────────────────────────
   La coupe devient horizontale, les cases passent dessous. */
@container billets (max-width: 700px) {
  .billet.a-cases { grid-template-columns: var(--b-talon) minmax(0, 1fr) var(--b-menu); }
  .billet.a-cases .corps { border-right: 0; padding-block: var(--space-2); }
  .billet .corps::before, .billet .corps::after { display: none; }
  .billet.a-cases .zones { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-column: 1 / -1;
    grid-row: 2; border-top: 2px dashed var(--color-border); min-height: var(--b-haut); }
  /* Les cases passent dessous : les coins du bas sont à elles. La pastille
     entre dans le talon, sous l'icône : à cheval sur la coupe, elle couvrait
     l'étiquette de la première case (vu au banc, choix de Cyril, 29/09). */
  .billet.a-cases .talon { border-radius: var(--radius-lg) 0 0 0; align-content: center; gap: var(--space-1); }
  .billet.a-cases .talon > .pastille { position: static; transform: none; box-shadow: none; }
  .billet.a-cases .billet-menu { border-radius: 0 var(--radius-lg) 0 0; }
  .billet.a-cases .zones > .depot-carre:first-child { border-radius: 0 0 0 var(--radius-lg); }
  .billet.a-cases .zones > .depot-carre:last-child { border-radius: 0 0 var(--radius-lg) 0; }
}

/* ── La demande de commande, `g-dm` (spec 2026-10-03-billets-d-objet, lot 1) ──
   Sortie de rouzes-chantiers/css/chantiers.css à rendu IDENTIQUE. La liste
   (`.dm-billets` de la fiche, `#filAchats` de Commandes) porte le conteneur
   `billets` dans la feuille de son module. */
.dm-grp { margin: var(--space-4) 0 var(--space-2); font: 700 var(--fs-2xs) var(--font-body);
  letter-spacing: .06em; text-transform: uppercase; color: var(--color-muted-fg); }
.dm-grp:first-child { margin-top: 0; }
.dm-grp .dm-compte { font-family: var(--font-mono); font-weight: 600; }
/* Le corps est un BOUTON (clic = déplier les lignes) : il garde la coupe. */
.billet:is(.g-dm, .g-b, .g-c) .corps { width: 100%; border: 0; border-right: 2px dashed var(--color-border);
  background: none; font-family: inherit; font-size: inherit; line-height: inherit;
  color: inherit; text-align: left; cursor: pointer; }
.billet:is(.g-dm, .g-b, .g-c) .corps:hover .ch-nom { color: var(--color-primary); }
.billet:is(.g-dm, .g-b, .g-c) .corps:focus-visible { outline: 2px solid var(--color-ring); outline-offset: -2px; }
/* CSB-05 : le commentaire entier est en tête du dépli ; ici, coupé, avec title. */
.dm-depli .bord-note { margin: 0 0 var(--space-2); white-space: pre-wrap; overflow-wrap: anywhere; }
.billet:is(.g-dm, .g-b) .detail .com { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.billet:is(.g-dm, .g-b) .depot-carre { padding-block: var(--space-15); }
.billet:is(.g-dm, .g-b) .depot-carre .marque { color: var(--color-ok-fg); }
.billet:is(.g-dm, .g-b) .depot-carre[data-refus] .val { color: var(--color-danger-fg); }
/* La jauge SEGMENTÉE de la case Commandé : un segment par ligne. */
.dm-jauge { display: flex; gap: var(--space-05); flex: 1 1 auto; margin-top: var(--space-1); }
.dm-jauge i { flex: 1; height: 6px; border-radius: var(--radius-full);
  background: var(--color-muted); border: 1px solid var(--color-border); }
.dm-jauge i[data-niv="plein"] { background: var(--color-primary); border-color: var(--color-primary); }
.dm-jauge i[data-niv="part"] { border-color: var(--color-primary);
  background: linear-gradient(90deg, var(--color-primary) 50%, var(--color-muted) 50%); }
/* La date souhaitée, en pilule à la taille de la sous-ligne. */
.dm-ech { font: 600 var(--fs-3xs)/1.5 var(--font-mono); padding: 0 var(--space-1); border-radius: var(--radius-full); }
.dm-ech.retard { background: var(--color-danger-bg); color: var(--color-danger-fg); }
.dm-ech.bientot { background: var(--color-warn-bg); color: var(--color-warn-fg); }
.dm-ech.loin { background: var(--color-muted); color: var(--color-muted-fg); }
.billet:is(.g-dm, .g-b) .sous .etiquette-navette { font-size: var(--fs-3xs); }
/* Le dépli, sous le billet : en lecture dans la fiche (`.dm-depli`), l'outil
   de commande dans le fil des achats (`.dm-outil`). Même cadre. */
.dm-depli, .dm-outil { margin: calc(-1 * var(--space-4)) 0 var(--space-5) var(--b-talon);
  padding: var(--space-4) var(--space-3) var(--space-2);
  background: var(--color-surface); border: 1px solid var(--color-border); border-top: 2px dashed var(--color-border);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg); overflow-x: auto; }
/* Déplié, le billet et son dépli ne font qu'UN (demande Cyril, 05/10/2026) :
   plus d'interstice — le pointillé du dépli recouvre le bas du billet comme
   une ligne de découpe, et le coin droit du billet se met d'équerre. La marge
   annule celle du billet (8 px, 24 px avec cases) plus son bord d'1 px. */
.billet + :is(.dm-depli, .dm-outil) { margin-top: calc(-1 * var(--space-2) - 1px); }
.billet.a-cases + :is(.dm-depli, .dm-outil) { margin-top: calc(-1 * var(--space-5) - 1px); }
.billet:has(+ :is(.dm-depli, .dm-outil):not([hidden])) { border-bottom-right-radius: 0; }
.dm-depli table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.dm-depli th { text-align: left; font: 700 var(--fs-3xs) var(--font-body); letter-spacing: .06em;
  text-transform: uppercase; color: var(--color-muted-fg); padding: var(--space-1) var(--space-2); }
.dm-depli td { padding: var(--space-1) var(--space-2); border-top: 1px solid var(--color-border); white-space: nowrap; }
.dm-depli td.n { font-family: var(--font-mono); text-align: right; }
.dm-depli tr.servie td { color: var(--color-muted-fg); }
.dm-depli-pied { display: flex; justify-content: flex-end; margin: var(--space-2) 0 0; }
@container billets (max-width: 700px) {
  .billet:is(.g-dm, .g-b) .depot-carre { padding-inline: var(--space-2); }
  /* Cases de ~86 px au téléphone : « → Commander » et « → Relancée 2× »
     débordaient sur la case voisine, « 1/3 lignes » se coupait (banc du
     03/10/2026). Valeur et invite en 11 px, flèche effacée — comme le billet
     de facture (rouzes-stats, g-f). */
  .billet:is(.g-dm, .g-b) .depot-carre .val, .billet:is(.g-dm, .g-b) .depot-carre .invite { font-size: var(--fs-2xs); }
  .billet:is(.g-dm, .g-b) .depot-carre .invite .ico-depot { display: none; }
  /* La coupe devient horizontale : la règle du corps-bouton ci-dessus (même
     spécificité) remettait le pointillé vertical. */
  .billet:is(.g-dm, .g-b, .g-c) .corps { border-right: 0; }
  .dm-depli, .dm-outil { margin-left: 0; }
  /* Dépli pleine largeur : le coin gauche suit, talon compris. */
  .billet:has(+ :is(.dm-depli, .dm-outil):not([hidden])),
  .billet:has(+ :is(.dm-depli, .dm-outil):not([hidden])) > .talon { border-bottom-left-radius: 0; }
}

/* ── La commande, `g-c` (lot 2, spec 2026-10-03-billets-d-objet) ──
   Venues de rouzes-commandes/css/commandes.css : le billet de commande se
   peint aussi dans la fiche chantier. Le mot d’état, et le verrou d’une zone
   fermée. */
.billet .etat-mot { font-weight: 600; }
.ico-marque { width: 12px; height: 12px; display: block; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ── Le bon atelier, `g-b` (lot 3, spec 2026-10-03-billets-d-objet) ──
   Il partage l’habit de la demande (corps-bouton, jauge, cases) : les règles
   `:is(.g-dm, .g-b)` ci-dessus. Ici, ce qui n’est qu’à lui : le bon « au
   crayon » du carnet (pointillés), et son bandeau d’état dans l’outil. */
.bon-bloc.bon-crayon > .billet { border-style: dashed; }
.bon-outil > .bon-planif { margin-top: 0; }
/* Dans une case (86 à 112 px), l’étiquette d’un départ tient sur UNE ligne :
   sans le camion (la case dit déjà « Expédié »), arrêt et code côte à côte —
   au banc du 03/10, elle faisait un pavé de 55 px sur deux lignes. */
.billet.g-b .sous .etiquette-navette { padding: 0 var(--space-1); }
.billet.g-b .sous .etiquette-navette .ico { display: none; }
.billet.g-b .sous .etiquette-navette-route { flex-wrap: nowrap; gap: var(--space-1); }
/* Téléphone (cases de 86 px) : « Rodez SE38 » demandait 78 px pour 66. */
@container billets (max-width: 700px) {
  .billet.g-b .sous .etiquette-navette { padding: 0 var(--space-05); }
  .billet.g-b .sous .etiquette-navette-route { gap: var(--space-05); }
  .billet.g-b .sous .etiquette-navette-code { padding-inline: var(--space-05); }
}

/* ── Le dépli de tout billet (lot 4 : le corps déplie, partout) ── */
.billet-bloc > .dm-depli[hidden] { display: none; }
.dm-depli th.n { text-align: right; }
.dm-depli td.ecart { color: var(--color-warn-fg); font-weight: 600; }
.dm-depli .cde-pied { margin: var(--space-2) 0 0; font-size: var(--fs-xs); color: var(--color-muted-fg); }
.dm-depli td small { color: var(--color-muted-fg); }

/* ── Le chevron des billets qui se déplient (04/10/2026, demande de Cyril) ──
   Le corps d'un billet dépliable porte `aria-expanded` (demande, commande,
   bon) : un « › » suit le nom du chantier et tourne d'un quart quand le
   billet est ouvert. Il vit dans la MARGE droite du nom (absolu dans le
   padding, que `overflow: hidden` ne rogne pas) : un nom long garde son
   « … » et le chevron reste visible ; le nom ne prend que sa largeur. */
.billet .corps[aria-expanded] .ch-nom { position: relative; align-self: flex-start; max-width: 100%;
  padding-right: var(--space-4); }
.billet .corps[aria-expanded] .ch-nom::after { content: "›"; position: absolute; right: var(--space-1); top: 50%;
  transform: translateY(-50%); color: var(--color-muted-fg); font-weight: 700;
  transition: transform var(--transition-fast); }
.billet .corps[aria-expanded="true"] .ch-nom::after { transform: translateY(-50%) rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .billet .corps[aria-expanded] .ch-nom::after { transition: none; }
}

/* ==================== socle-erp/css/facture-client.css ==================== */
/* Socle ERP — la fenêtre « Déposer une facture » et le voile du dépôt
 * (facture-client-vue.js, spec 2026-10-01-facture-client-pdf §4).
 * SOURCE UNIQUE, bundle design-system.css (sync.mjs, ORDRE). */
.fc-dlg { width: min(680px, calc(100vw - 2 * var(--space-4))); }
.fc-form { display: grid; gap: var(--space-3); padding: var(--space-4); }
.fc-titre { margin: 0; }
.fc-fichier { margin: 0; font-size: var(--fs-sm); color: var(--color-muted-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc-corps { display: grid; gap: var(--space-3); }
.fc-rang { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-3); }
.fc-corps select, .fc-corps input { width: 100%; min-width: 0; }
.fc-montants { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.fc-montants th, .fc-montants td { padding: var(--space-1) var(--space-2);
  border-bottom: 1px solid var(--color-border); text-align: right; }
.fc-montants th:first-child { text-align: left; }
.fc-montants input { text-align: right; font-family: var(--font-mono); }
.fc-ref, .fc-ecart { font-family: var(--font-mono); white-space: nowrap; }
.fc-ref { color: var(--color-muted-fg); }
.fc-ecart[data-ecart="egal"] { color: var(--color-ok-fg); }
.fc-ecart[data-ecart="diff"] { color: var(--color-warn-fg); font-weight: 600; }
.fc-notes { margin: 0; padding-left: var(--space-4); display: grid; gap: var(--space-1);
  font-size: var(--fs-xs); color: var(--color-muted-fg); }
.fc-joint, .fc-alerte, .fc-erreur { margin: 0; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md); font-size: var(--fs-sm); }
.fc-joint { background: var(--color-info-bg); color: var(--color-info-fg); }
.fc-alerte, .fc-erreur { background: var(--color-danger-bg); color: var(--color-danger-fg); }
.fc-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
.fc-corps [hidden] { display: none; }
/* Le voile : toute la page accueille le PDF pendant qu'on le glisse. */
.fc-voile { position: fixed; inset: var(--space-2); z-index: 60; display: grid; place-items: center;
  border: 3px dashed var(--color-primary); border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
  color: var(--color-primary); font-size: var(--fs-lg); font-weight: 600; pointer-events: none; }
/* Téléphone : le tableau des montants s'EMPILE. À quatre colonnes il ne reste
 * que 40 à 70 px à l'input « Lu sur le PDF » (les colonnes Attendu et Écart,
 * insécables, prennent leur largeur) et les montants lus sont coupés. Chaque
 * ligne devient : le libellé et l'input sur toute la largeur, puis l'attendu et
 * l'écart côte à côte, étiquetés par l'intitulé de leur colonne (data-lib).
 * Même seuil que le reste du socle (composants.css). */
@media (max-width: 640px) {
  .fc-montants, .fc-montants tbody { display: block; }
  .fc-montants thead { display: none; }
  .fc-montants tr { display: grid; grid-template-columns: var(--space-6) 1fr 1fr; align-items: center;
    column-gap: var(--space-3); row-gap: var(--space-1); padding: var(--space-2) 0;
    border-bottom: 1px solid var(--color-border); }
  .fc-montants th, .fc-montants td { display: block; min-width: 0; padding: 0; border: 0; text-align: left; }
  .fc-montants td:nth-child(2) { grid-column: 2 / -1; }
  .fc-montants td:nth-child(n+3) { grid-row: 2; }
  .fc-montants td:nth-child(3) { grid-column: 2; }
  .fc-montants td:nth-child(4) { grid-column: 3; }
  .fc-ref::before, .fc-ecart::before { content: attr(data-lib); display: block; margin-bottom: var(--space-1);
    font-family: var(--font-body); font-size: var(--fs-xs); font-weight: 400; color: var(--color-muted-fg); white-space: normal; }
  .fc-ecart:empty { display: none; }
}

/* ==================== socle-erp/css/onglets.css ==================== */
/* Socle ERP — les onglets « intercalaires de classeur » et leur volet
   (nés dans le dossier Chantiers, partagés avec la page atelier le
   09/09/2026). Chaque volet se saisit par sa languette, coins hauts
   arrondis. L'onglet actif prend le fond du volet et masque la bordure
   qui les sépare — c'est ce raccord qui fait lire les deux comme une
   seule pièce. Plusieurs languettes : la barre défile au besoin plutôt
   que de se replier sur deux lignes. */
.fiche-onglets {
  display: flex; gap: var(--space-05); margin: var(--space-4) 0 0;
  overflow-x: auto; scrollbar-width: none;
}
.fiche-onglets::-webkit-scrollbar { display: none; }
/* `flex: 1 1 auto` : les languettes se partagent TOUTE la largeur du volet,
   en gardant leurs proportions. À l'étroit, la barre défile au lieu de
   tronquer les libellés. Le raccord ne repose PAS sur un débord de la
   languette : `overflow-x` fait de cette barre un conteneur de défilement,
   qui rognerait tout ce qui dépasse. C'est le volet qui renonce à sa
   bordure haute, et chaque languette qui porte la sienne — celle de
   l'onglet ouvert prend la couleur du fond. */
.fiche-onglet {
  flex: 1 1 auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--color-muted); cursor: pointer; font: inherit;
  padding: var(--space-2) var(--space-3); color: var(--color-muted-fg);
  white-space: nowrap;
  transition: color var(--transition-fast), background var(--transition-fast);
}
.fiche-onglet:hover { color: var(--color-foreground); }
.fiche-onglet.est-actif {
  background: var(--color-surface); color: var(--color-foreground);
  font-weight: 600;
  border-bottom-color: var(--color-surface);
}
/* Coins hauts CARRÉS et sans bordure : les languettes couvrent toute la
   largeur, un rayon ici ne servait qu'à creuser un trou sous celle de
   gauche. `touch-action: manipulation` (iPhone, 27/08/2026) : sans elle,
   Safari attend un éventuel double tap avant de délivrer le clic. */
.fiche-volet {
  background: var(--color-surface);
  border: 1px solid var(--color-border); border-top: 0;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  padding: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-4);
  touch-action: manipulation;
}

/* Fiche moins lourde (19/08/2026, décision Cyril) : au repos, un champ ne
   montre qu'un fond doux et un filet bas — soixante cadres complets
   faisaient de la fiche une grille de mots croisés. Le cadre entier revient
   sous la souris et au focus, là où l'on agit. Les cases et les radios
   gardent leur dessin natif. */
.fiche-volet input:not([type="checkbox"]):not([type="radio"]), .fiche-volet select, .fiche-volet textarea {
  padding: var(--space-2) var(--space-2);
  border: 1px solid transparent;
  border-bottom-color: var(--color-border);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: color-mix(in srgb, var(--color-muted) 55%, transparent);
  color: var(--color-foreground);
  font: inherit; font-size: var(--fs-sm); resize: vertical; width: 100%;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.fiche-volet input:not([type="checkbox"]):not([type="radio"]):hover, .fiche-volet select:hover,
.fiche-volet textarea:hover { border-color: var(--color-border); }
.fiche-volet input:not([type="checkbox"]):not([type="radio"]):focus, .fiche-volet select:focus,
.fiche-volet textarea:focus {
  border-color: var(--color-ring); background: var(--color-surface);
  border-radius: var(--radius-md); outline: none;
}
.fiche-volet :disabled { opacity: .7; cursor: default; }

/* ==================== socle-erp/css/tableau-debit.css ==================== */
/* Socle ERP — le tableau de débit : une seule apparence de la fiche publiée
   au bon de préparation (spec 2026-09-08). Recopié par sync.mjs dans
   css/socle/ des modules qui le <link>ent, APRÈS composants.css.
   Ces règles vivaient en trois exemplaires (debit.css, chantiers.css,
   commandes.css), identiques au caractère près — le jour où l'une aurait
   bougé, les autres seraient restées en arrière sans erreur. */

/* Bandeau de section (ALUMINIUM À COUPER, PANNEAUX…) des tableaux qui ne
   sont pas le tableau de débit. Le bandeau de ZONE, tr.section.section-zone,
   reste dans composants.css. */
.tableau tr.section td {
  background: var(--color-muted); font-weight: 600;
  font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .04em;
}
/* La référence en mono, comme les nombres : c'est un code, pas une phrase. */
.tableau td.ref { font-family: var(--font-mono); font-size: var(--fs-sm); white-space: nowrap; }

/* ── Le tableau de débit : « lisible d'un coup d'œil » (choix de Cyril,
   08/09/2026, banc essais-ui/banc-tableau-debit-propositions.html, A).
   Hiérarchie typographique : la QUANTITÉ domine (gras, un cran plus grand,
   unité en petit gris), la référence est un code (pilule mono), les cotes
   s'effacent en gris, les sections deviennent des titres soulignés de la
   couleur du module au lieu d'un aplat gris, une ligne sur deux est teintée.
   Rien de structurel : les quatre écrans et le papier en héritent. ──────── */
.tableau-debit tr.section td {
  background: transparent; color: var(--color-primary); font-size: var(--fs-xs);
  letter-spacing: .08em; padding: var(--space-5) var(--space-3) var(--space-2); border-top: 0;
  border-bottom: 2px solid color-mix(in srgb, var(--color-primary) 35%, transparent);
}
/* Le bandeau de zone reprend le dessus du bandeau de section : deux classes
   contre une, sans dépendre de l'ordre (même leçon qu'en composants.css). */
.tableau-debit tr.section.section-zone td {
  background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
  color: var(--color-foreground); text-transform: none; letter-spacing: 0;
  font-size: var(--fs-sm); font-weight: 700; padding: var(--space-3); border-bottom: 0;
  border-radius: var(--radius-sm);
}
/* `nth-of-type` compte aussi les bandeaux : le zébrage repart à chaque
   section, ce qui aide à lire plutôt que l'inverse. */
.tableau-debit tbody tr:not(.section):nth-of-type(even) td {
  background: color-mix(in srgb, var(--color-muted) 45%, transparent);
}
.tableau-debit td { border-top: 0; padding-top: var(--space-2); padding-bottom: var(--space-2); font-variant-numeric: tabular-nums; }
.tableau-debit td.ref { font-size: var(--fs-xs); }
.tableau-debit td.ref .code { display: inline-block; padding: 1px var(--space-2); border-radius: var(--radius-full); background: var(--color-muted); }
.tableau-debit td.designation { font-weight: 500; }
.tableau-debit td.cotes { color: var(--color-muted-fg); font-size: var(--fs-xs); }
.tableau-debit td.qte { font-weight: 700; font-size: var(--fs-md); }
.tableau-debit td.qte .unite { font-weight: 400; font-size: var(--fs-2xs); color: var(--color-muted-fg); }
/* ── L'alignement de la colonne Qté (04/10/2026, Cyril : « que tout
   s'aligne bien ») : l'unité occupe un EMPLACEMENT fixe à droite du nombre,
   si bien que les nombres s'alignent d'une ligne à l'autre (« 13 u » et
   « 4 ml ») ; une ligne sans unité réserve le même emplacement, et l'en-tête
   « Qté » s'arrête au-dessus des nombres, pas au-dessus des unités.
   `--qte-unite` = 3 caractères de l'unité ; l'espace qui précède l'unité
   (gardée pour le papier et la copie texte) est en police MONO (td.num) :
   0,6 em de la quantité — mesuré au banc, 0,25 em laissait 5,6 px d'écart. */
.tableau-debit { --qte-unite: calc(var(--fs-2xs) * 3); --qte-reserve: calc(var(--qte-unite) + var(--fs-md) * .6); }
.tableau-debit td.qte .unite { display: inline-block; min-width: var(--qte-unite); text-align: left; }
.tableau-debit td.qte:not(:has(.unite)), .tableau-debit th.qte { padding-right: calc(var(--space-3) + var(--qte-reserve)); }
/* Les flèches +/− d'un champ numérique gardent leur place même cachées :
   le chiffre saisi s'arrêtait ~15 px avant celui des lignes en lecture
   (04/10/2026, Cyril : « aligner les chiffres ensemble, pas les unités »).
   Sans elles, tous les chiffres d'une colonne tombent au même bord ; les
   flèches du clavier gardent leur effet. */
.tableau-debit td.num input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.tableau-debit td.num input[type="number"]::-webkit-inner-spin-button,
.tableau-debit td.num input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.tableau-debit td.num:not(.qte):not(.cotes) { color: var(--color-muted-fg); }

/* Badges d'état d'une ligne : seulement les COULEURS. La base `.badge` vit
   dans composants.css (pilule à hauteur fixe, texte centré par `inline-flex`).
   Elle était redéfinie ici en `inline-block` + `padding: 1px`, et comme cette
   feuille est concaténée APRÈS composants.css dans le bundle, c'est cette
   version-là qui gagnait partout : le texte des pilules se posait à 1 px du
   haut et 8 px du bas, dans les neuf modules. Deux vérités pour un même
   sélecteur nu du socle — le piège déjà rencontré sur `.entete`. (17/09/2026) */
/* Le TEXTE des pilules prend l'encre du triplet de sa famille (plus foncée que
   la teinte autonome) : la teinte autonome sur sa propre teinte à 14-16 %
   tombait à 3,5-4,5:1, sous le seuil AA du petit texte (CSA-08). `.badge-ok`
   n'est plus redéfini ici : la version de composants.css (triplet ok) fait foi. */
.badge-cmd { background: color-mix(in srgb, var(--color-primary) 14%, transparent); color: var(--color-primary-hover); }
.badge-part { background: color-mix(in srgb, var(--color-warning) 16%, transparent); color: var(--color-warn-fg); }
.badge-alerte { background: color-mix(in srgb, var(--color-destructive) 14%, transparent); color: var(--color-danger-fg); }
.badge-attente { background: color-mix(in srgb, var(--rz-ardoise-900) 8%, transparent); color: var(--color-muted-fg); }
/* « à saisir » / « à détailler » : quelqu'un complète après moi. */
.badge-saisir { background: var(--color-muted); color: var(--color-primary-hover); }

/* ── Changer la référence à la volée (04/10/2026, socle/choix-reference.js) :
   la pilule de la réf. devient un bouton là où la ligne s'édite, et ouvre une
   recherche dans la bibliothèque. Même pilule que `.code`, avec un trait
   pointillé qui dit « cliquable » sans crier. ─────────────────────────── */
.tableau td .ref-choix, .tableau-debit td.ref .ref-choix {
  display: inline-block; padding: 1px var(--space-2); border-radius: var(--radius-full);
  border: 1px dashed color-mix(in srgb, var(--color-primary) 45%, var(--color-border));
  background: var(--color-muted); color: var(--color-foreground);
  font: inherit; font-family: var(--font-mono); font-size: var(--fs-xs);
  cursor: pointer; white-space: nowrap;
}
.tableau td .ref-choix:hover, .tableau td .ref-choix:focus-visible {
  border-style: solid; border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
}
.choix-ref {
  position: fixed; z-index: var(--z-toast); display: grid; gap: var(--space-2);
  padding: var(--space-3); background: var(--color-surface);
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
/* Le champ prend toute la largeur : les modules donnent à `.saisie` la
   taille d'une quantité (76 px, aligné à droite). */
.choix-ref input { width: 100%; text-align: left; }
.choix-ref ul { list-style: none; margin: 0; padding: 0; max-height: 260px; overflow-y: auto; }
.choix-ref li[role="option"] {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: var(--space-2); padding: var(--space-2); border-radius: var(--radius-sm); cursor: pointer;
}
.choix-ref li[aria-selected="true"], .choix-ref li[role="option"]:hover {
  background: color-mix(in srgb, var(--color-primary) 10%, var(--color-surface));
}
.choix-ref .code { font-family: var(--font-mono); font-size: var(--fs-xs); padding: 1px var(--space-2);
  border-radius: var(--radius-full); background: var(--color-muted); white-space: nowrap; }
.choix-ref-des { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-sm); }
.choix-ref .unite { color: var(--color-muted-fg); font-size: var(--fs-2xs); }
.choix-ref-alerte { margin: 0; padding: var(--space-2); border-radius: var(--radius-sm); font-size: var(--fs-2xs);
  background: var(--color-warn-bg); color: var(--color-warn-fg); }
.choix-ref-vide, .choix-ref-pied { color: var(--color-muted-fg); font-size: var(--fs-2xs); padding: var(--space-1) var(--space-2); margin: 0; }

/* ==================== socle-erp/css/frise-chargement.css ==================== */
/* Socle ERP — la frise d'un départ de la navette Sud (spec 2026-09-09 ; visuel
   « tableau des départs » choisi par Cyril le 09/09 au soir, allégé le 10/09).
   La ligne du trajet est une ROUTE : une bande de 4 px, verte où c'est roulé,
   bleue avec des tirets clairs qui défilent sur le tronçon en cours (la seule
   animation, coupée par prefers-reduced-motion), un tracé fin en tirets pour
   l'itinéraire à venir. Les nœuds sont des jalons posés sur la bande — cochés
   quand c'est fait —, les lieux s'écrivent comme sur un panneau (Archivo,
   capitales), les heures en chasse fixe, et le camion (une semi-remorque en
   silhouette) roule SUR la route, roues au bitume. Chaque nœud porte ses deux
   demi-tronçons (`avant-*`, `apres-*`). En préparation la frise se règle sur
   place (⇄, ×, agence fantôme). Suivent la pilule d'état d'une ligne et le
   tableau de lecture d'un bon. Jetons du socle uniquement. */
.frise{display:flex;align-items:flex-start;padding:var(--space-5) 0 var(--space-4);margin:0}
/* Géométrie : nœud paddé 44 en haut ; route 52→56 (4 px) ; point 20 px centré
   à 54 ; camion 84×32 posé à 22 → ses roues (30,4 dans le viewBox) touchent la
   route ; à quai, posé à 6 : garé au-dessus du jalon sans le toucher. */
.frise-noeud{flex:1 1 0;min-width:0;position:relative;display:flex;flex-direction:column;align-items:center;text-align:center;padding:44px var(--space-1) 0}
/* L'itinéraire à venir : un tracé fin en tirets (2 px, tuile 12 plein / 8 vide)
   au milieu de la bande. La tuile est ancrée à GAUCHE sur le demi-tronçon de
   droite et à DROITE sur celui de gauche : le motif traverse la jonction sans
   tiret coupé. */
.frise-noeud::before,.frise-noeud::after{content:"";position:absolute;top:52px;width:50%;height:4px}
.frise-noeud::before{left:0;background:linear-gradient(90deg,color-mix(in srgb, var(--color-muted-fg) 42%, transparent) 0 12px,transparent 12px) left center / 20px 2px repeat-x}
.frise-noeud::after{right:0;background:linear-gradient(90deg,color-mix(in srgb, var(--color-muted-fg) 42%, transparent) 0 12px,transparent 12px) right center / 20px 2px repeat-x}
.frise-noeud.premier::before,.frise-noeud.dernier::after{display:none}
/* Roulé : asphalte vert, plein et calme. */
.frise-noeud.avant-fait::before,.frise-noeud.apres-fait::after{background:var(--color-success)}
/* En cours : bleu d'action, des tirets clairs (tuile 16 : 8 plein / 8 vide) qui
   défilent vers l'avant, dans le sens de la marche. */
.frise-noeud.avant-en_cours::before,.frise-noeud.apres-en_cours::after{background:linear-gradient(90deg,color-mix(in srgb, var(--color-surface) 45%, transparent) 0 8px,transparent 8px) 0 50% / 16px 100% repeat-x,var(--color-primary)}
.frise-noeud.apres-en_cours::after{background-position:100% 50%}
@keyframes route-avant{to{background-position:16px 50%}}
@keyframes route-apres{to{background-position:calc(100% + 16px) 50%}}
@media (prefers-reduced-motion:no-preference){
  .frise-noeud.avant-en_cours::before{animation:route-avant 1.6s linear infinite}
  .frise-noeud.apres-en_cours::after{animation:route-apres 1.6s linear infinite}
}
/* Les jalons : un disque sur la route. Fait = plein vert coché ; en cours =
   anneau bleu et halo ; à venir = anneau neutre. */
.frise-point{position:relative;z-index:1;width:20px;height:20px;box-sizing:border-box;border-radius:50%;border:3px solid var(--color-border);background:var(--color-surface)}
.frise-noeud.fait .frise-point{background:var(--color-success);border-color:var(--color-success)}
.frise-noeud.fait .frise-point::after{content:"";position:absolute;left:4.5px;top:1.5px;width:3.5px;height:7.5px;border:solid var(--color-surface);border-width:0 2.5px 2.5px 0;transform:rotate(45deg)}
.frise-noeud.en_cours .frise-point{border-color:var(--color-primary);box-shadow:0 0 0 5px color-mix(in srgb, var(--color-primary) 16%, transparent)}
/* Les lieux s'écrivent comme sur un panneau : Archivo, la police des titres de
   l'ERP, en capitales espacées ; la destination en cours prend le bleu du camion. */
.frise-libelle{margin-top:var(--space-3);font:700 var(--fs-sm)/1.2 "Archivo",var(--font-body);text-transform:uppercase;letter-spacing:.06em}
.frise-noeud.a_venir .frise-libelle{color:var(--color-muted-fg)}
.frise-noeud.en_cours .frise-libelle{color:var(--color-primary)}
.frise-etat{margin-top:var(--space-1);max-width:100%;font:500 var(--fs-xs) var(--font-mono);color:var(--color-muted-fg);line-height:1.5;letter-spacing:-.01em;white-space:pre-line}
/* Le camion : semi-remorque en silhouette d'encre ; vitres, jantes et phare
   découpés dans la surface. À cheval sur le nœud qu'il rejoint (tronçon en
   cours), roues au bitume ; à quai, garé au-dessus du jalon où il attend, sans
   le toucher. */
.frise-camion{position:absolute;top:22px;width:84px;height:32px;fill:var(--color-foreground)}
.frise-camion .vitre,.frise-camion .jante,.frise-camion .phare{fill:var(--color-surface)}
.frise-camion.camion-avant{left:0;transform:translateX(-50%)}
.frise-camion.camion-quai{top:6px;left:50%;transform:translateX(-50%)}
/* En préparation, la frise se règle sur place. ⇄ : une petite pilule posée sur
   la route, à la jonction de deux agences, inverse leur ordre. × : au survol
   d'un jalon d'agence sans bon, retire l'arrêt. Une agence hors trajet reste
   en transparence au bout de la route (nœud fantôme) : un clic la remet. */
.frise-permuter{position:absolute;top:54px;left:100%;transform:translate(-50%,-50%);z-index:2;height:22px;padding:0 var(--space-2);border:1px solid var(--color-border);border-radius:var(--radius-full);background:var(--color-surface);color:var(--color-muted-fg);font:600 var(--fs-sm)/1 var(--font-body);cursor:pointer;transition:color var(--transition-fast),border-color var(--transition-fast)}
.frise-permuter:hover,.frise-permuter:focus-visible{color:var(--color-primary);border-color:var(--color-primary)}
.frise-retirer{position:absolute;top:38px;left:calc(50% + 10px);z-index:2;width:18px;height:18px;padding:0;border:1px solid var(--color-border);border-radius:var(--radius-full);background:var(--color-surface);color:var(--color-muted-fg);font:600 var(--fs-xs)/1 var(--font-body);cursor:pointer;opacity:0;transition:opacity var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast)}
.frise-noeud:hover .frise-retirer,.frise-retirer:focus-visible{opacity:1}
.frise-retirer:hover,.frise-retirer:focus-visible{color:var(--color-danger-fg);border-color:var(--color-danger-fg)}
@media (hover:none){.frise-retirer{opacity:1}}
/* Palier tactile (CSA-15) : × 18 px et ⇄ 22 px sont sous le seuil de 24 px de
   WCAG 2.5.8. La pastille reste petite à l'œil ; une zone transparente la
   porte à 44 px. Les jalons sont écartés d'au moins un quart de la largeur :
   la zone ne mord pas sur la cible voisine. Par la largeur, pas par
   `pointer: coarse` (la Surface Pro répond « tactile » en permanence). */
@media (max-width:640px){
  .frise-retirer{opacity:1}
  .frise-retirer::after{content:"";position:absolute;inset:-13px}
  .frise-permuter{min-width:44px}
  .frise-permuter::after{content:"";position:absolute;inset:-11px 0}
}
.frise-noeud.fantome{border:0;background:none;color:inherit;font:inherit;cursor:pointer;opacity:.45;transition:opacity var(--transition-fast)}
.frise-noeud.fantome:hover,.frise-noeud.fantome:focus-visible{opacity:.85}
.frise-noeud.fantome .frise-point{border-style:dashed}
.frise-noeud.fantome .frise-libelle,.frise-noeud.fantome .frise-etat{color:var(--color-muted-fg)}
.frise-noeud.avant-fantome::before{opacity:.5}
/* Compact : une ligne dans le dossier du chantier. */
.frise-compacte{display:inline-flex;align-items:center;gap:var(--space-1);font-size:var(--fs-xs);color:var(--color-muted-fg)}
.frise-compacte .frise-camion{position:static;transform:none;width:36px;height:14px;flex:none}
.frise-compacte b{color:var(--color-foreground)}
/* Étroit (tablette en portrait, ~700 px) : un nœud fait ~140 px. Camion réduit, lieux plus petits. */
@media (max-width:760px){
  .frise-camion{width:63px;height:24px;top:29px}
  .frise-camion.camion-quai{top:16px}
  .frise-libelle{font-size:var(--fs-xs)}
}
/* L'état d'une ligne : à préparer (neutre), chargé (marque), reçu (attente), remis (fait). */
.pilule{display:inline-block;padding:var(--space-05) var(--space-25);border-radius:var(--radius-full);font-size:var(--fs-xs);font-weight:600;white-space:nowrap;line-height:1.4;background:var(--color-muted);color:var(--color-muted-fg)}
.pilule-charge{background:var(--color-primary);color:var(--color-on-primary)}
.pilule-recu_agence{background:color-mix(in srgb, var(--color-warning) 14%, transparent);color:var(--color-warn-fg)}
.pilule-remis{background:color-mix(in srgb, var(--color-success) 14%, transparent);color:var(--color-ok-fg)}
/* Le contenu d'un bon en lecture : des lignes fines, la réf en chasse fixe, la
   quantité qui se lit d'un coup, les cotes en retrait. Pas de zébrage. */
.bon-lecture{width:100%;border-collapse:collapse;font-size:var(--fs-sm)}
.bon-lecture th{text-align:left;font-size:var(--fs-2xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--color-muted-fg);padding:var(--space-1) var(--space-2) var(--space-15);border-bottom:1px solid var(--color-border)}
.bon-lecture td{padding:var(--space-15) var(--space-2);border-bottom:1px solid color-mix(in srgb, var(--color-border) 60%, transparent);vertical-align:baseline}
.bon-lecture tr:last-child td{border-bottom:0}
.bon-lecture td.ref{font:500 var(--fs-xs) var(--font-mono);color:var(--color-muted-fg);white-space:nowrap;width:6rem}
.bon-lecture .num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.bon-lecture td.num{font-weight:600}
.bon-lecture td.num small{font-weight:400;color:var(--color-muted-fg);margin-left:2px}
.bon-lecture td.cotes{font:500 var(--fs-xs) var(--font-mono);color:var(--color-muted-fg);width:8rem}
.bon-lecture-section td{padding-top:var(--space-2);font-size:var(--fs-2xs);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--color-muted-fg);border-bottom:0}

/* ==================== socle-erp/css/taches.css ==================== */
/* Socle ERP — la carte de tâche, partagée par le hub (tableau de bord,
   répartition) et l'onglet Suivi du module Chantiers (lot B, 09/09/2026).
   Les six jetons de texte du hub (--sourdine, --encre, --azur, --ligne,
   --texte, --mono) n'existent QUE dans hub.css : chaque usage passe ici par
   un jeton local `--t-*` qui retombe sur les jetons du socle. Le hub les
   rebranche sur sa palette dans hub.css (chargée APRÈS cette feuille) et
   garde son rendu au pixel ; les autres modules prennent le socle. */

/* Les cinq tailles MESUREES du hub (19/09/2026). Elles tombent ENTRE les
   barreaux de l'echelle --fs-* : le tableau de bord a ete cale au pixel
   dessus (cf. .tache-delai, 11,5 px entre --fs-2xs et --fs-xs) ; les quetes,
   le bandeau et les trophees qui lisaient les autres sont retires le
   03/10/2026. Une definition `--x:` est le SEUL lieu ou un
   litteral est legitime ; les ecrire ici, nommees, vaut mieux que de les
   cacher dans un raccourci `font:` que la regle `font-size` ne voit pas.
   Une reprise de l'echelle devra trancher ces cinq barreaux intermediaires. */
:root{
  --t-fs-10-5: 10.5px;
  --t-fs-11-5: 11.5px;
  --t-fs-12-5: 12.5px;
  --t-fs-14:   14px;
  --t-fs-14-5: 14.5px;
}

.tache{
  --t-sourdine: var(--color-muted-fg);
  --t-encre:    var(--color-foreground);
  --t-azur:     var(--color-primary);
  --t-ligne:    var(--color-border);
  --t-texte:    var(--font-body);
  --t-mono:     var(--font-mono);
}

/* Restyle du 27/08/2026 (décision Cyril, canevas « Pistes notifications » :
   l'existant croisé avec la piste B). La bordure gauche colorée disparaît —
   c'est la TUILE-ICÔNE, teintée pastel par gravité, qui porte le niveau ;
   la carte s'arrondit et son filet s'adoucit. Le DOM ne change pas d'un
   octet (carteTache est partagée avec la Répartition). */
.tache{
  display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  background:#fff;border:1px solid #E9EDF3;
  border-radius:12px;padding:var(--space-25) var(--space-3);margin-bottom:var(--space-2);
  transition:transform var(--dur-2,150ms) var(--ease-sortie,ease),
             box-shadow var(--dur-2,150ms),border-color var(--dur-2,150ms);
}
.tache:hover{transform:translateY(-1px);box-shadow:0 3px 10px rgba(15,23,42,.07);border-color:#C9D6EA}
.tache.epinglee{background:#FFFDF5}
.tache > .ico{
  width:30px;height:30px;flex:none;box-sizing:border-box;
  padding:var(--space-2);border-radius:9px;
  color:var(--t-sourdine);background:#EFF4FB;
}
.tache.niv-urgent > .ico{color:#C2273B;background:#FDF1F0}
.tache.niv-bientot > .ico{color:var(--color-warning);background:#FDF6E7}
/* `flex:1 1 12rem` et non `flex:1` : avec `min-width:0` seul, le délai et les
   boutons (tous deux `flex:none`) écrasaient le corps à ZÉRO sous ~520 px —
   titre et explication de la tâche devenaient invisibles, et les boutons
   partaient hors écran. Le plancher de 12rem force le retour à la ligne. */
.tache-corps{min-width:0;flex:1 1 12rem}
.tache-titre{margin:0;font:500 var(--fs-sm) var(--t-texte);color:var(--t-encre)}
.tache-chantier{font-weight:400;color:var(--t-sourdine)}
.tache-note{margin:var(--space-05) 0 0;font-size:var(--fs-xs);color:var(--t-sourdine);display:flex;gap:var(--space-15);flex-wrap:wrap}
.tache-auto{font-size:var(--fs-3xs);background:#F1F5FB;color:var(--color-muted-fg);border-radius:var(--radius-full);padding:0 var(--space-2)}
.tache-attente{font-size:var(--fs-2xs);color:#B54708}
/* 11.5 px : entre --fs-2xs (11) et --fs-xs (12), la taille mesurée du hub. */
.tache-delai{font-weight:500;font-size:var(--t-fs-11-5);font-family:var(--t-mono);line-height:normal;color:var(--t-sourdine);white-space:nowrap;flex:none}
.tache.niv-urgent .tache-delai{color:var(--color-danger-fg)}
.tache-actions{display:flex;align-items:center;gap:var(--space-1);flex:none}
.tache-action{
  border:1px solid #E9EDF3;background:#FBFCFE;color:var(--t-encre);
  font:500 var(--fs-xs) var(--t-texte);padding:var(--space-15) var(--space-3);border-radius:var(--radius-full);cursor:pointer;
  text-decoration:none;
}
.tache-action:hover{border-color:var(--t-azur);color:var(--t-azur)}
/* Suppression administrateur (Tâche 14b) : même rouge que `.lien.danger`
   (composants.css du socle) — la couleur du geste irréversible ne doit pas
   changer de nuance d'un écran à l'autre. Forme pilule conservée : ce
   bouton vit parmi Fait/Valider, pas parmi des liens de texte. */
.tache-supprimer{border-color:#F3C6C0;color:var(--color-danger-fg)}
.tache-supprimer:hover{border-color:var(--color-danger-fg);color:#7A160F}
.tache-epingle{border:0;background:none;color:var(--color-muted-fg);font-size:var(--fs-body);cursor:pointer;padding:0 var(--space-05)}
/* Deux états lisibles sans la couleur seule : ☆ au contour au repos (muted-fg,
   6,2:1 sur blanc), ★ plein épinglée (ambre-700, 5,0:1) (CSA-14). */
.tache-epingle.on{color:var(--rz-ambre-700)}

/* Sur écran étroit, délai et boutons passent EN DESSOUS du texte plutôt que
   de le comprimer. Ce bloc est écrit ICI, après les règles de `.tache` : une
   média-requête n'ajoute pas de spécificité, un bloc placé plus haut aurait
   été battu par les `flex:none` déclarés plus bas. */
@media (max-width:640px){
  .tache-delai{flex:1 0 100%}
  .tache-actions{flex:1 0 100%;justify-content:flex-end;flex-wrap:wrap}
}

/* Au doigt : les actions des cartes de tâches. Mesuré le 27/08/2026 sur le
   tableau de bord CONNECTÉ, à 375 px — `.tache-action` faisait 22 px de haut,
   `.tache-epingle` 16×19. Ce sont les gestes les plus fréquents de l'écran le
   plus fréquenté. Seule la HAUTEUR est portée à 44 px : `.tache-actions` est
   `flex:none`, élargir les boutons rognerait `.tache-corps`.
   LA HAUTEUR ENTRE DANS LA CONDITION (02/09/2026) : un téléphone COUCHÉ fait
   812 px de large et ne déclenchait aucune de ces règles — c'est toujours un
   pouce qui appuie. `pointer:coarse` mentait sur les Surface Pro. */
@media (max-width: 640px), (max-height: 520px) {
  .tache-action {
    display: inline-flex; align-items: center;
    min-height: 44px; padding-block: 0;
  }
  .tache-epingle { min-width: 32px; min-height: 44px; }
}

/* Le fil de commentaires (lot B) */
.tache-fil{margin-top:var(--space-1);font-size:var(--fs-xs)}
.tache-fil > summary{cursor:pointer;color:var(--t-azur);list-style:none;display:inline-block;
  padding:0 var(--space-1);border-radius:var(--radius-full);border:1px solid transparent}
.tache-fil > summary::-webkit-details-marker{display:none}
.tache-fil > summary:hover{border-color:var(--t-azur)}
.fil-liste{list-style:none;margin:var(--space-1) 0 0;padding:0;display:grid;gap:var(--space-1)}
.fil-liste li{padding:var(--space-1) var(--space-15);background:var(--color-muted);border-radius:8px}
.fil-liste p{margin:var(--space-05) 0 0;color:var(--t-encre);white-space:pre-wrap}
.fil-qui{font-weight:600;color:var(--t-encre)}
.fil-quand{margin-left:var(--space-1);color:var(--t-sourdine);font:500 var(--fs-2xs) var(--t-mono)}
.fil-saisie{display:flex;gap:var(--space-1);align-items:flex-start;margin-top:var(--space-1)}
.fil-saisie textarea{flex:1 1 auto;min-width:0;font:inherit;font-size:var(--fs-xs);
  padding:var(--space-1);border:1px solid var(--t-ligne);border-radius:8px;resize:vertical}

/* La puce « mon service » — même gabarit que `.tache-auto`, dont elle est
   la voisine immédiate dans la note. Elle remplace le suffixe des titres
   de niveau retirés le 10/09/2026. */
.tache-service{font-size:var(--fs-3xs);background:#F1F5FB;
  color:var(--color-muted-fg);border-radius:var(--radius-full);
  padding:0 var(--space-2);white-space:nowrap}

/* ==================== socle-erp/css/impression.css ==================== */
/* ══ GABARIT D'IMPRESSION — socle ERP ═════════════════════════════════════
   Source unique de la mise en page PAPIER des dix documents de l'ERP.
   Recopié dans chaque module par `node socle-erp/sync.mjs`, et seulement là
   où une page le réclame par <link href="css/socle/impression.css">.

   Avant ce fichier (22/08/2026), l'impression existait en TROIS variantes :
   - un squelette `.feuille` correct, recopié mot pour mot dans
     chantiers.css et commandes.css (PV, bordereau, bon d'atelier) ;
   - `#printSheet` au planning, en pixels ;
   - et, pour la fiche de débit et l'intercalaire de fiche chantier,
     « j'imprime l'écran en masquant .entete » : ni titre, ni logo, ni date,
     ni en-tête de colonnes en page 2. Aucun des neuf documents ne portait
     le logo, sauf le plan client (jsPDF).
   Le même bloc « masquer .entete » était en outre recopié dans
   commandes.css, où il ne désignait AUCUN élément de la page : du décor
   qu'on aurait fini par croire actif.

   ── LE SQUELETTE ────────────────────────────────────────────────────────
   Une seule géométrie, deux densités, montées par js/socle/impression.js :

     .doc.externe   papier à en-tête complet — le document SORT de
                    l'entreprise (PV de réception, bordereau de
                    sous-traitance, plan client). Il porte l'émetteur de
                    l'agence, lu en base (table `agences`).
     .doc.interne   en-tête d'une ligne — il circule dans l'atelier (fiche
                    du jour, semaine, livraisons, débit, achats, bon de
                    préparation). Gagne 22 mm, soit six lignes de tableau.

   ── LES RÈGLES QUI TIENNENT PARTOUT ─────────────────────────────────────
   - tout en POINTS : 11 pt corps, 9,5 pt tableaux, 9 pt notes, 8 pt
     libellés, 16 pt titre. Un px d'écran ne veut rien dire sur une feuille.
   - noir sur blanc ; le gris #555 est réservé aux LIBELLÉS, jamais à la
     donnée — une valeur grise se photocopie mal et se lit mal en atelier.
   - un seul aplat, #eee, pour les en-têtes de colonnes et les lignes de
     section, avec `print-color-adjust: exact` : sans lui Chrome les
     blanchit et la hiérarchie du tableau disparaît.
   - `thead` en `table-header-group` : l'en-tête des colonnes se réimprime
     en tête de CHAQUE page. Sans quoi la page 2 d'un débit est une grille
     de chiffres sans nom de colonne.
   - `break-inside: avoid` sur chaque ligne, chaque bloc de renseignement et
     chaque cadre de signature ; `break-after: avoid` sur les titres, qui ne
     restent donc jamais seuls en bas de page.
   - le PIED est dans le flux, une fois, en fin de document. Un pied
     répandu sur chaque page demanderait `position: fixed`, que Chrome
     répète bien mais qui passe SOUS le texte dès qu'une page est pleine :
     l'identité du document serait imprimée par-dessus une ligne de débit.
     Ce que chaque page porte, c'est l'en-tête des colonnes (ci-dessus).
   - pas de « page X/Y » : Chrome ne sait pas compter les pages en CSS
     (`counter(pages)` n'est pas implémenté). La pagination reste celle du
     navigateur, si l'utilisateur la coche.
   ═════════════════════════════════════════════════════════════════════════ */

/* La feuille n'existe QUE pour le papier. Elle vit à la racine du <body>
   (hors .page) et le js la remplit au moment du geste. */
.feuille { display: none; }

@media print {
  @page { size: A4; margin: 14mm 14mm 16mm; }
  /* Le paysage est DISPONIBLE, pas imposé : aucun des documents actuels n'en
     a besoin (la semaine atelier est une liste par jour, pas une grille à
     sept colonnes). Il attend le premier tableau trop large — `class="doc
     interne paysage"` suffira. */
  @page paysage { size: A4 landscape; }
  .doc.paysage { page: paysage; }

  /* Le masquage du reste de la page est conditionné à un état DÉCLARÉ, le
     temps du geste « Imprimer » (le js pose .impression sur le body). Sans
     cette condition, un Ctrl+P sur l'écran des achats imprimait une page
     blanche — ou le bordereau resté en mémoire.
     `display: block` sur le body : un module qui se met en flex ou en
     grille écraserait sinon la feuille dans une colonne de la grille. */
  body.impression {
    display: block !important;
    height: auto !important;
    overflow: visible !important;
    background: #fff !important;
  }
  body.impression > *:not(.feuille) { display: none !important; }
  body.impression .feuille { display: block; }

  /* Ctrl+P sans passer par un bouton : plutôt que de laisser sortir l'écran
     tel quel — colonnes tronquées, barres d'action, aucun en-tête — on
     imprime une ligne qui dit où est le geste. Le planning faisait déjà
     exactement cela ; c'est ici pour les cinq autres modules.
     Le ::before du body survit au masquage de ses ENFANTS.
     Réservé aux pages qui ONT un hôte d'impression (`.feuille` à la racine du
     body) : Admin, Bibliothèque, Stats et le Hub n'ont ni bouton « Imprimer »
     ni feuille, et un Ctrl+P y sortait une page blanche renvoyant à un bouton
     inexistant. Sans hôte, la page s'imprime telle quelle (CSA-07). */
  body:not(.impression):has(> .feuille) > * { display: none !important; }
  body:not(.impression):has(> .feuille)::before {
    content: "Pour imprimer, utilisez le bouton « Imprimer » de l'écran : "
      "le document sort alors avec son en-tête, son logo, sa date et ses "
      "totaux — et l'en-tête des colonnes se répète à chaque page.";
    display: block; padding: 10mm; font-size: 11pt; line-height: 1.5;
    color: #000;
  }

  .doc {
    color: #000; background: #fff;
    /* Les JETONS, pas une police nommée en dur : « Inter » n'est installée
       sur aucun poste du parc, et une police absente ne se plaint pas — elle
       tombe silencieusement sur le repli suivant. Le papier porterait donc
       une autre police que l'écran, et le PV du client une autre que le
       bordereau du sous-traitant. */
    font-family: var(--font-body, system-ui, -apple-system, "Segoe UI", sans-serif);
    font-size: 11pt; line-height: 1.4;
  }
  /* Une feuille peut porter PLUSIEURS documents (deux PV d'un même
     chantier, un lot de bons) : chacun repart en tête de page, jamais collé
     sous le pied du précédent. */
  .feuille .doc + .doc { break-before: page; }

  .doc h1, .doc h2, .doc h3 {
    font-family: var(--font-titres, var(--font-body, system-ui, sans-serif));
    letter-spacing: -.01em;
  }

  /* ── En-tête ─────────────────────────────────────────────────────────
     Le logo est un <svg> INLINE, pas une image de fond : Chrome n'imprime
     les fonds que si « Graphiques d'arrière-plan » est coché dans la boîte
     d'impression. Le logo d'un PV signé par un client ne peut pas dépendre
     d'une case à cocher. Ce n'est pas non plus un <img src> : un 404 sur
     l'asset donnerait le même document nu, sans la moindre erreur. */
  .doc-logo { display: block; width: 34mm; height: auto; flex: none; }

  .doc.externe .doc-tete {
    display: flex; justify-content: space-between; align-items: flex-start;
    padding-bottom: 4mm; margin-bottom: 4mm;
    border-bottom: 1.5pt solid #000;
  }
  .doc-emetteur { text-align: right; font-size: 8.5pt; line-height: 1.35; color: #333; }
  .doc-emetteur strong {
    display: block; font-size: 10pt; color: #000; letter-spacing: .04em;
  }

  .doc-titre {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 10mm; margin-bottom: 7mm;
  }
  .doc-titre h1 { margin: 0; font-size: 16pt; font-weight: 700; }
  .doc-objet { margin: 1mm 0 0; font-size: 10pt; }

  /* Un numéro de chantier est une CLÉ qu'on relit à l'oral et qu'on
     recopie : chasse fixe, comme partout ailleurs dans l'ERP. */
  .doc-ref { font-family: var(--font-mono, ui-monospace, monospace); font-size: 9pt; }

  /* État civil du document (référence, date, agence) : des couples
     libellé/valeur alignés à droite, jamais un tableau — rien ne s'y somme. */
  .doc-meta { margin: 0; font-size: 9pt; display: grid; gap: .8mm; justify-items: end; }
  .doc-meta div { display: flex; gap: 3mm; align-items: baseline; }
  .doc-meta dt {
    font-size: 7.5pt; text-transform: uppercase; letter-spacing: .06em;
    color: #555; min-width: 22mm; text-align: right;
  }
  .doc-meta dd { margin: 0; font-weight: 600; }

  .doc.interne .doc-tete {
    display: flex; align-items: center; gap: 6mm;
    padding-bottom: 3mm; margin-bottom: 5mm;
    border-bottom: 1.5pt solid #000;
  }
  .doc.interne .doc-logo { width: 26mm; }
  .doc.interne .doc-tete h1 { margin: 0; font-size: 14pt; }
  .doc.interne .doc-objet { font-size: 9.5pt; color: #333; }
  .doc.interne .doc-meta { margin-left: auto; flex: none; }

  /* ── Corps ───────────────────────────────────────────────────────────*/
  .doc h2 {
    font-size: 11.5pt; margin: 6mm 0 2mm; padding-bottom: 1mm;
    border-bottom: .75pt solid #000; break-after: avoid;
  }
  .doc h2:first-of-type { margin-top: 0; }

  /* `.feuille .tableau` suit `.f-table` : les fiches de débit et d'achat
     CLONENT le tableau affiché à l'écran plutôt que de le reconstruire —
     une seule vérité pour ce qui est montré et ce qui est imprimé. Le
     tableau d'écran porte la classe `tableau` du socle, il hérite donc ici
     de l'habillage papier sans qu'on réécrive ses classes. */
  .f-table, .feuille .tableau {
    width: 100%; border-collapse: collapse; margin-bottom: 5mm;
    font-size: 9.5pt; line-height: 1.25;
  }
  .f-table thead, .feuille .tableau thead { display: table-header-group; }
  .f-table tr, .feuille .tableau tr { break-inside: avoid; }
  .f-table th, .f-table td,
  .feuille .tableau th, .feuille .tableau td {
    border: .5pt solid #999; padding: 1.6mm 2.4mm; text-align: left;
  }
  .f-table th, .feuille .tableau th {
    background: #eee; print-color-adjust: exact; -webkit-print-color-adjust: exact;
    font-size: 8pt; text-transform: uppercase; letter-spacing: .04em;
  }
  /* `.n` est la classe des documents montés ici ; `.num` celle des tableaux
     d'écran clonés (fiche de débit, fiche d'achat). Les deux disent la même
     chose — un nombre se lit aligné à droite, sur une chasse tabulaire, ou
     bien l'œil ne compare plus les ordres de grandeur. */
  .f-table .n, .feuille .tableau .n,
  .f-table .num, .feuille .tableau .num {
    text-align: right; font-variant-numeric: tabular-nums;
  }
  /* Les codes d'atelier et les références catalogue : chasse fixe, comme à
     l'écran et comme dans .doc-ref — on les relit à l'oral et on les recopie. */
  .feuille .tableau .ref, .feuille .tableau .code {
    font-family: var(--font-mono, ui-monospace, monospace); font-size: 8.5pt;
  }
  .f-table tr.section td, .feuille .tableau tr.section td {
    background: #eee; print-color-adjust: exact; -webkit-print-color-adjust: exact;
    font-weight: 700;
  }
  .f-table tfoot td, .feuille .tableau tfoot td {
    font-weight: 700; border-top: 1.25pt solid #000;
  }
  /* Le libellé d'un total se colle à son chiffre : posé à gauche, il faut
     traverser cinq colonnes vides pour savoir de quoi 14 843,04 est le
     total. */
  .f-table tfoot td:first-child, .feuille .tableau tfoot td:first-child {
    text-align: right;
  }
  .f-table td.vide { color: #999; }

  /* Ce qui n'a aucun sens sur du papier, dans un écran cloné : les boutons
     d'action et la pastille « qui a fait quoi », qui est une affordance
     d'écran. Les cases à cocher, ELLES, restent : sur un intercalaire de
     fiche chantier, une case cochée est une donnée, pas une commande. */
  .feuille button, .feuille .btn, .feuille .pastille,
  .feuille .lien { display: none !important; }

  /* Un champ de saisie cloné s'imprime comme du TEXTE : sa valeur est déjà
     là, seul son cadre est un artefact d'écran. Sans cette règle, la fiche
     client sortait en chapelet de rectangles gris — et sur une fiche figée
     (elle se lit verrouillée), ces cadres promettaient une saisie possible
     à qui tenait la feuille. */
  .feuille input, .feuille select, .feuille textarea {
    border: 0 !important; background: none !important; box-shadow: none !important;
    padding: 0 !important; min-height: 0 !important; height: auto !important;
    font: inherit; color: #000; appearance: none; -webkit-appearance: none;
    resize: none;
  }
  .feuille input[type="checkbox"], .feuille input[type="radio"] {
    appearance: auto; -webkit-appearance: auto;
    print-color-adjust: exact; -webkit-print-color-adjust: exact;
  }
  /* Un textarea ne s'étire pas à l'impression : il coupe son texte à sa
     hauteur d'écran, en silence. Le js le remplace par un <p> avant de
     cloner (voir clonerPourPapier() dans js/socle/impression.js). */
  .feuille .f-texte { white-space: pre-wrap; margin: 0; }

  /* Un champ cloné garde la mise en page que la feuille du MODULE lui donne
     à l'écran — mais dès que celle-ci repose sur des règles d'écran (flex,
     grille), le papier se retrouve avec le libellé collé à la valeur :
     « ResponsableM. Fabre ». Sur du papier, un renseignement se lit comme
     partout ailleurs dans ces documents : libellé en petites capitales
     grises, valeur dessous. */
  .feuille label { display: block; margin-bottom: 2.5mm; break-inside: avoid; }
  .feuille label > span:first-child {
    display: block; font-size: 7.5pt; text-transform: uppercase;
    letter-spacing: .06em; color: #555; margin-bottom: .5mm;
  }

  /* Blocs de renseignement en vis-à-vis (parties, conditions, totaux) */
  .f-parties { display: flex; gap: 10mm; margin-bottom: 6mm; break-inside: avoid; }
  .f-parties > div { flex: 1; }
  .f-parties span {
    display: block; font-size: 7.5pt; text-transform: uppercase;
    letter-spacing: .06em; color: #555; margin-bottom: .8mm;
  }

  .f-note { font-size: 9pt; color: #333; margin: 0 0 8mm; }

  /* On signe sur un TRAIT, on remplit un cadre : une ligne, pas un
     rectangle en pointillés. */
  .f-signatures { display: flex; gap: 12mm; margin-top: 4mm; break-inside: avoid; }
  .f-signatures > div {
    flex: 1; border-top: .75pt solid #000; padding-top: 1.5mm; min-height: 24mm;
  }
  .f-signatures span { font-size: 8.5pt; color: #555; }
  .f-signatures img { max-height: 22mm; display: block; margin-top: 2mm; }

  /* ── Listes à pointer (atelier, préparation, livraisons) ─────────────*/
  .f-liste { list-style: none; margin: 0 0 5mm; padding: 0; }
  .f-liste li {
    display: flex; align-items: flex-start; gap: 3mm;
    padding: 1.8mm 0; border-bottom: .5pt solid #ccc; break-inside: avoid;
  }
  .f-case {
    width: 5mm; height: 5mm; border: 1pt solid #000; border-radius: .8mm;
    flex: none; margin-top: .6mm;
  }
  /* Tâche déjà pointée à l'écran : case noircie sur le papier. */
  .f-case.faite {
    background: #000; print-color-adjust: exact; -webkit-print-color-adjust: exact;
  }
  .f-tache { font-weight: 700; font-size: 10pt; }
  .f-detail { font-size: 9pt; color: #333; }
  .f-sous-titre {
    list-style: none; margin: 3mm 0 .5mm; padding: 0; font-weight: 700;
    font-size: 8.5pt; text-transform: uppercase; letter-spacing: .05em;
  }

  /* ── Pied ────────────────────────────────────────────────────────────*/
  .doc-pied {
    margin-top: 8mm; padding-top: 2mm; border-top: .5pt solid #999;
    font-size: 7.5pt; color: #555;
    display: flex; justify-content: space-between; gap: 6mm;
    break-inside: avoid;
  }
}

/* ── L'accusé de réception de commande (07/09/2026, F5) ──────────────────
   Un bloc destinataire à droite (la fenêtre d'une enveloppe), un récapitulatif
   à deux colonnes libellé/valeur — rien ne s'y somme, donc pas un .f-table.
   Tout en points, comme le reste du gabarit. Hors du bloc @media : ces
   classes n'existent que dans la feuille d'impression, jamais à l'écran. */
.doc .arc-dest { margin: 0 0 8mm 55%; font-size: 10pt; line-height: 1.35; }
.doc .arc p { margin: 0 0 4pt; font-size: 10.5pt; line-height: 1.4; }
.doc .arc-recap { border-collapse: collapse; margin: 5mm 0; font-size: 10pt; }
.doc .arc-recap th { text-align: left; font-weight: 600; padding: 2pt 8mm 2pt 0;
                     white-space: nowrap; }
.doc .arc-recap td { padding: 2pt 0; }
